1#!/usr/local/bin/python3.8 2# -*- coding: utf-8 -*- 3 4# Carla host code 5# Copyright (C) 2011-2021 Filipe Coelho <falktx@falktx.com> 6# 7# This program is free software; you can redistribute it and/or 8# modify it under the terms of the GNU General Public License as 9# published by the Free Software Foundation; either version 2 of 10# the License, or any later version. 11# 12# This program is distributed in the hope that it will be useful, 13# but WITHOUT ANY WARRANTY; without even the implied warranty of 14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15# GNU General Public License for more details. 16# 17# For a full copy of the GNU General Public License see the doc/GPL.txt file. 18 19# ------------------------------------------------------------------------------------------------------------ 20# Imports (Global) 21 22import json 23 24# ------------------------------------------------------------------------------------------------------------ 25# Imports (ctypes) 26 27from ctypes import ( 28 byref, pointer 29) 30 31# ------------------------------------------------------------------------------------------------------------ 32# Imports (PyQt5) 33 34# This fails in some configurations, assume >= 5.6.0 in that case 35try: 36 from PyQt5.Qt import PYQT_VERSION 37except ImportError: 38 PYQT_VERSION = 0x50600 39 40from PyQt5.QtCore import ( 41 QT_VERSION, qCritical, QBuffer, QEventLoop, QFileInfo, QIODevice, QMimeData, QModelIndex, QPointF, QTimer, QEvent 42) 43from PyQt5.QtGui import ( 44 QImage, QImageWriter, QPainter, QPalette, QBrush 45) 46from PyQt5.QtWidgets import ( 47 QAction, QApplication, QInputDialog, QFileSystemModel, QListWidgetItem, QGraphicsView, QMainWindow 48) 49 50# ------------------------------------------------------------------------------------------------------------ 51# Imports (Custom) 52 53import ui_carla_host 54 55from carla_app import * 56from carla_backend_qt import CarlaHostQtDLL, CarlaHostQtNull 57from carla_database import * 58from carla_settings import * 59from carla_utils import * 60from carla_widgets import * 61 62from patchcanvas import patchcanvas 63from widgets.digitalpeakmeter import DigitalPeakMeter 64from widgets.pixmapkeyboard import PixmapKeyboardHArea 65 66# ------------------------------------------------------------------------------------------------------------ 67# Try Import OpenGL 68 69try: 70 from PyQt5.QtOpenGL import QGLWidget 71 hasGL = True 72except: 73 hasGL = False 74 75# ------------------------------------------------------------------------------------------------------------ 76# Safe exception hook, needed for PyQt5 77 78def sys_excepthook(typ, value, tback): 79 return sys.__excepthook__(typ, value, tback) 80 81# ------------------------------------------------------------------------------------------------------------ 82# Session Management support 83 84CARLA_CLIENT_NAME = os.getenv("CARLA_CLIENT_NAME") 85LADISH_APP_NAME = os.getenv("LADISH_APP_NAME") 86NSM_URL = os.getenv("NSM_URL") 87 88# ------------------------------------------------------------------------------------------------------------ 89# Small print helper 90 91def processMode2Str(processMode): 92 if processMode == ENGINE_PROCESS_MODE_SINGLE_CLIENT: 93 return "Single client" 94 if processMode == ENGINE_PROCESS_MODE_MULTIPLE_CLIENTS: 95 return "Multi client" 96 if processMode == ENGINE_PROCESS_MODE_CONTINUOUS_RACK: 97 return "Continuous Rack" 98 if processMode == ENGINE_PROCESS_MODE_PATCHBAY: 99 return "Patchbay" 100 if processMode == ENGINE_PROCESS_MODE_BRIDGE: 101 return "Bridge" 102 return "Unknown" 103 104# ------------------------------------------------------------------------------------------------------------ 105# Carla Print class 106 107class CarlaPrint: 108 def __init__(self, err): 109 self.err = err 110 111 def flush(self): 112 gCarla.utils.fflush(self.err) 113 114 def write(self, string): 115 gCarla.utils.fputs(self.err, string) 116 117# ------------------------------------------------------------------------------------------------------------ 118# Host Window 119 120class HostWindow(QMainWindow): 121#class HostWindow(QMainWindow, PluginEditParentMeta, metaclass=PyQtMetaClass): 122 # signals 123 SIGTERM = pyqtSignal() 124 SIGUSR1 = pyqtSignal() 125 126 # CustomActions 127 CUSTOM_ACTION_NONE = 0 128 CUSTOM_ACTION_APP_CLOSE = 1 129 CUSTOM_ACTION_PROJECT_LOAD = 2 130 131 # -------------------------------------------------------------------------------------------------------- 132 133 def __init__(self, host, withCanvas, parent=None): 134 QMainWindow.__init__(self, parent) 135 self.host = host 136 self.ui = ui_carla_host.Ui_CarlaHostW() 137 self.ui.setupUi(self) 138 gCarla.gui = self 139 140 if False: 141 # kdevelop likes this :) 142 host = CarlaHostNull() 143 self.host = host 144 145 self._true = c_char_p("true".encode("utf-8")) 146 147 self.fParentOrSelf = parent or self 148 149 # ---------------------------------------------------------------------------------------------------- 150 # Internal stuff 151 152 self.fIdleTimerNull = self.startTimer(1000) # keep application signals alive 153 self.fIdleTimerFast = 0 154 self.fIdleTimerSlow = 0 155 156 self.fLadspaRdfNeedsUpdate = True 157 self.fLadspaRdfList = [] 158 159 self.fPluginCount = 0 160 self.fPluginList = [] 161 162 self.fPluginDatabaseDialog = None 163 self.fFavoritePlugins = [] 164 165 self.fProjectFilename = "" 166 self.fIsProjectLoading = False 167 self.fCurrentlyRemovingAllPlugins = False 168 169 self.fLastTransportBPM = 0.0 170 self.fLastTransportFrame = 0 171 self.fLastTransportState = False 172 self.fBufferSize = 0 173 self.fSampleRate = 0.0 174 self.fOscAddressTCP = "" 175 self.fOscAddressUDP = "" 176 177 if MACOS: 178 self.fMacClosingHelper = True 179 180 # CancelableActionCallback Box 181 self.fCancelableActionBox = None 182 183 # run a custom action after engine is properly closed 184 self.fCustomStopAction = self.CUSTOM_ACTION_NONE 185 186 # first attempt of auto-start engine doesn't show an error 187 self.fFirstEngineInit = True 188 189 # to be filled with key-value pairs of current settings 190 self.fSavedSettings = {} 191 192 # true if NSM server handles our window management 193 self.fWindowCloseHideGui = False 194 195 if host.isControl: 196 self.fClientName = "Carla-Control" 197 self.fSessionManagerName = "Control" 198 elif host.isPlugin: 199 self.fClientName = "Carla-Plugin" 200 self.fSessionManagerName = "Plugin" 201 elif LADISH_APP_NAME: 202 self.fClientName = LADISH_APP_NAME 203 self.fSessionManagerName = "LADISH" 204 elif NSM_URL and host.nsmOK: 205 self.fClientName = "Carla.tmp" 206 self.fSessionManagerName = "Non Session Manager TMP" 207 self.fWindowCloseHideGui = True 208 else: 209 self.fClientName = CARLA_CLIENT_NAME or "Carla" 210 self.fSessionManagerName = "" 211 212 # ---------------------------------------------------------------------------------------------------- 213 # Internal stuff (patchbay) 214 215 self.fPeaksCleared = True 216 217 self.fExternalPatchbay = False 218 self.fSelectedPlugins = [] 219 220 self.fCanvasWidth = 0 221 self.fCanvasHeight = 0 222 self.fMiniCanvasUpdateTimeout = 0 223 224 self.fWithCanvas = withCanvas 225 226 # ---------------------------------------------------------------------------------------------------- 227 # Internal stuff (logs) 228 229 self.autoscrollOnNewLog = True 230 self.lastLogSliderPos = 0 231 232 # ---------------------------------------------------------------------------------------------------- 233 # Set up GUI (engine stopped) 234 235 if self.host.isPlugin or self.host.isControl: 236 self.ui.act_file_save.setVisible(False) 237 self.ui.act_engine_start.setEnabled(False) 238 self.ui.act_engine_start.setVisible(False) 239 self.ui.act_engine_stop.setEnabled(False) 240 self.ui.act_engine_stop.setVisible(False) 241 self.ui.menu_Engine.setEnabled(False) 242 self.ui.menu_Engine.setVisible(False) 243 self.ui.menu_Engine.menuAction().setVisible(False) 244 self.ui.tabWidget.removeTab(2) 245 246 if self.host.isControl: 247 self.ui.act_file_new.setVisible(False) 248 self.ui.act_file_open.setVisible(False) 249 self.ui.act_file_save_as.setVisible(False) 250 self.ui.tabUtils.removeTab(0) 251 else: 252 self.ui.act_file_save_as.setText(self.tr("Export as...")) 253 254 if not withCanvas: 255 self.ui.tabWidget.tabBar().hide() 256 257 else: 258 self.ui.act_engine_start.setEnabled(True) 259 260 if WINDOWS: 261 self.ui.tabWidget.removeTab(2) 262 263 if self.host.isControl: 264 self.ui.act_file_refresh.setEnabled(False) 265 else: 266 self.ui.act_file_connect.setEnabled(False) 267 self.ui.act_file_connect.setVisible(False) 268 self.ui.act_file_refresh.setEnabled(False) 269 self.ui.act_file_refresh.setVisible(False) 270 271 if self.fSessionManagerName and not self.host.isPlugin: 272 self.ui.act_file_new.setEnabled(False) 273 274 self.ui.act_file_open.setEnabled(False) 275 self.ui.act_file_save.setEnabled(False) 276 self.ui.act_file_save_as.setEnabled(False) 277 self.ui.act_engine_stop.setEnabled(False) 278 self.ui.act_plugin_remove_all.setEnabled(False) 279 280 self.ui.act_canvas_show_internal.setChecked(False) 281 self.ui.act_canvas_show_internal.setVisible(False) 282 self.ui.act_canvas_show_external.setChecked(False) 283 self.ui.act_canvas_show_external.setVisible(False) 284 285 self.ui.menu_PluginMacros.setEnabled(False) 286 self.ui.menu_Canvas.setEnabled(False) 287 288 self.ui.dockWidgetTitleBar = QWidget(self) 289 self.ui.dockWidget.setTitleBarWidget(self.ui.dockWidgetTitleBar) 290 291 if not withCanvas: 292 self.ui.act_canvas_show_internal.setVisible(False) 293 self.ui.act_canvas_show_external.setVisible(False) 294 self.ui.act_canvas_arrange.setVisible(False) 295 self.ui.act_canvas_refresh.setVisible(False) 296 self.ui.act_canvas_save_image.setVisible(False) 297 self.ui.act_canvas_zoom_100.setVisible(False) 298 self.ui.act_canvas_zoom_fit.setVisible(False) 299 self.ui.act_canvas_zoom_in.setVisible(False) 300 self.ui.act_canvas_zoom_out.setVisible(False) 301 self.ui.act_settings_show_meters.setVisible(False) 302 self.ui.act_settings_show_keyboard.setVisible(False) 303 self.ui.menu_Canvas_Zoom.setEnabled(False) 304 self.ui.menu_Canvas_Zoom.setVisible(False) 305 self.ui.menu_Canvas_Zoom.menuAction().setVisible(False) 306 self.ui.menu_Canvas.setEnabled(False) 307 self.ui.menu_Canvas.setVisible(False) 308 self.ui.menu_Canvas.menuAction().setVisible(False) 309 self.ui.tw_miniCanvas.hide() 310 self.ui.tabWidget.removeTab(1) 311 if WINDOWS: 312 self.ui.tabWidget.tabBar().hide() 313 314 # ---------------------------------------------------------------------------------------------------- 315 # Set up GUI (disk) 316 317 exts = gCarla.utils.get_supported_file_extensions() 318 319 self.fDirModel = QFileSystemModel(self) 320 self.fDirModel.setRootPath(HOME) 321 self.fDirModel.setNameFilters(tuple(("*." + i) for i in exts)) 322 323 self.ui.fileTreeView.setModel(self.fDirModel) 324 self.ui.fileTreeView.setRootIndex(self.fDirModel.index(HOME)) 325 self.ui.fileTreeView.setColumnHidden(1, True) 326 self.ui.fileTreeView.setColumnHidden(2, True) 327 self.ui.fileTreeView.setColumnHidden(3, True) 328 self.ui.fileTreeView.setHeaderHidden(True) 329 330 # ---------------------------------------------------------------------------------------------------- 331 # Set up GUI (transport) 332 333 fontMetrics = self.ui.l_transport_bbt.fontMetrics() 334 minValueWidth = fontMetricsHorizontalAdvance(fontMetrics, "000|00|0000") 335 minLabelWidth = fontMetricsHorizontalAdvance(fontMetrics, self.ui.label_transport_frame.text()) 336 337 labelTimeWidth = fontMetricsHorizontalAdvance(fontMetrics, self.ui.label_transport_time.text()) 338 labelBBTWidth = fontMetricsHorizontalAdvance(fontMetrics, self.ui.label_transport_bbt.text()) 339 340 if minLabelWidth < labelTimeWidth: 341 minLabelWidth = labelTimeWidth 342 if minLabelWidth < labelBBTWidth: 343 minLabelWidth = labelBBTWidth 344 345 self.ui.label_transport_frame.setMinimumWidth(minLabelWidth + 3) 346 self.ui.label_transport_time.setMinimumWidth(minLabelWidth + 3) 347 self.ui.label_transport_bbt.setMinimumWidth(minLabelWidth + 3) 348 349 self.ui.l_transport_bbt.setMinimumWidth(minValueWidth + 3) 350 self.ui.l_transport_frame.setMinimumWidth(minValueWidth + 3) 351 self.ui.l_transport_time.setMinimumWidth(minValueWidth + 3) 352 353 if host.isPlugin: 354 self.ui.b_transport_play.setEnabled(False) 355 self.ui.b_transport_stop.setEnabled(False) 356 self.ui.b_transport_backwards.setEnabled(False) 357 self.ui.b_transport_forwards.setEnabled(False) 358 self.ui.group_transport_controls.setEnabled(False) 359 self.ui.group_transport_controls.setVisible(False) 360 self.ui.cb_transport_link.setEnabled(False) 361 self.ui.cb_transport_link.setVisible(False) 362 self.ui.cb_transport_jack.setEnabled(False) 363 self.ui.cb_transport_jack.setVisible(False) 364 self.ui.dsb_transport_bpm.setEnabled(False) 365 self.ui.dsb_transport_bpm.setReadOnly(True) 366 367 self.ui.w_transport.setEnabled(False) 368 369 # ---------------------------------------------------------------------------------------------------- 370 # Set up GUI (rack) 371 372 self.ui.listWidget.setHostAndParent(self.host, self) 373 374 sb = self.ui.listWidget.verticalScrollBar() 375 self.ui.rackScrollBar.setMinimum(sb.minimum()) 376 self.ui.rackScrollBar.setMaximum(sb.maximum()) 377 self.ui.rackScrollBar.setValue(sb.value()) 378 379 sb.rangeChanged.connect(self.ui.rackScrollBar.setRange) 380 sb.valueChanged.connect(self.ui.rackScrollBar.setValue) 381 self.ui.rackScrollBar.rangeChanged.connect(sb.setRange) 382 self.ui.rackScrollBar.valueChanged.connect(sb.setValue) 383 384 self.updateStyle() 385 386 self.ui.rack.setStyleSheet(""" 387 CarlaRackList#CarlaRackList { 388 background-color: black; 389 } 390 """) 391 392 # ---------------------------------------------------------------------------------------------------- 393 # Set up GUI (patchbay) 394 395 self.ui.peak_in.setChannelCount(2) 396 self.ui.peak_in.setMeterColor(DigitalPeakMeter.COLOR_BLUE) 397 self.ui.peak_in.setMeterOrientation(DigitalPeakMeter.VERTICAL) 398 self.ui.peak_in.setFixedWidth(25) 399 400 self.ui.peak_out.setChannelCount(2) 401 self.ui.peak_out.setMeterColor(DigitalPeakMeter.COLOR_GREEN) 402 self.ui.peak_out.setMeterOrientation(DigitalPeakMeter.VERTICAL) 403 self.ui.peak_out.setFixedWidth(25) 404 405 self.ui.scrollArea = PixmapKeyboardHArea(self.ui.patchbay) 406 self.ui.keyboard = self.ui.scrollArea.keyboard 407 self.ui.patchbay.layout().addWidget(self.ui.scrollArea, 1, 0, 1, 0) 408 409 self.ui.scrollArea.setEnabled(False) 410 411 self.ui.miniCanvasPreview.setRealParent(self) 412 self.ui.tw_miniCanvas.tabBar().hide() 413 414 # ---------------------------------------------------------------------------------------------------- 415 # Set up GUI (logs) 416 417 self.ui.text_logs.textChanged.connect(self.slot_logButtonsState) 418 self.ui.logs_clear.clicked.connect(self.slot_logClear) 419 self.ui.logs_save.clicked.connect(self.slot_logSave) 420 self.ui.logs_autoscroll.stateChanged.connect(self.slot_toggleLogAutoscroll) 421 self.ui.text_logs.verticalScrollBar().valueChanged.connect(self.slot_logSliderMoved) 422 423 # ---------------------------------------------------------------------------------------------------- 424 # Set up GUI (special stuff for Mac OS) 425 426 if MACOS: 427 self.ui.act_file_quit.setMenuRole(QAction.QuitRole) 428 self.ui.act_settings_configure.setMenuRole(QAction.PreferencesRole) 429 self.ui.act_help_about.setMenuRole(QAction.AboutRole) 430 self.ui.act_help_about_juce.setMenuRole(QAction.ApplicationSpecificRole) 431 self.ui.act_help_about_qt.setMenuRole(QAction.AboutQtRole) 432 self.ui.menu_Settings.setTitle("Panels") 433 434 # ---------------------------------------------------------------------------------------------------- 435 # Load Settings 436 437 self.loadSettings(True) 438 439 # ---------------------------------------------------------------------------------------------------- 440 # Set-up Canvas 441 442 if withCanvas: 443 self.scene = patchcanvas.PatchScene(self, self.ui.graphicsView) 444 self.ui.graphicsView.setScene(self.scene) 445 446 if self.fSavedSettings[CARLA_KEY_CANVAS_USE_OPENGL] and hasGL: 447 self.ui.glView = QGLWidget(self) 448 self.ui.graphicsView.setViewport(self.ui.glView) 449 450 self.setupCanvas() 451 452 # ---------------------------------------------------------------------------------------------------- 453 # Set-up Icons 454 455 if self.fSavedSettings[CARLA_KEY_MAIN_SYSTEM_ICONS]: 456 self.ui.act_file_connect.setIcon(getIcon('network-connect', 16, 'svgz')) 457 self.ui.act_file_refresh.setIcon(getIcon('view-refresh', 16, 'svgz')) 458 self.ui.act_file_new.setIcon(getIcon('document-new', 16, 'svgz')) 459 self.ui.act_file_open.setIcon(getIcon('document-open', 16, 'svgz')) 460 self.ui.act_file_save.setIcon(getIcon('document-save', 16, 'svgz')) 461 self.ui.act_file_save_as.setIcon(getIcon('document-save-as', 16, 'svgz')) 462 self.ui.act_file_quit.setIcon(getIcon('application-exit', 16, 'svgz')) 463 self.ui.act_engine_start.setIcon(getIcon('media-playback-start', 16, 'svgz')) 464 self.ui.act_engine_stop.setIcon(getIcon('media-playback-stop', 16, 'svgz')) 465 self.ui.act_engine_panic.setIcon(getIcon('dialog-warning', 16, 'svgz')) 466 self.ui.act_engine_config.setIcon(getIcon('configure', 16, 'svgz')) 467 self.ui.act_plugin_add.setIcon(getIcon('list-add', 16, 'svgz')) 468 self.ui.act_plugin_add_jack.setIcon(getIcon('list-add', 16, 'svgz')) 469 self.ui.act_plugin_remove_all.setIcon(getIcon('edit-delete', 16, 'svgz')) 470 self.ui.act_canvas_arrange.setIcon(getIcon('view-sort-ascending', 16, 'svgz')) 471 self.ui.act_canvas_refresh.setIcon(getIcon('view-refresh', 16, 'svgz')) 472 self.ui.act_canvas_zoom_fit.setIcon(getIcon('zoom-fit-best', 16, 'svgz')) 473 self.ui.act_canvas_zoom_in.setIcon(getIcon('zoom-in', 16, 'svgz')) 474 self.ui.act_canvas_zoom_out.setIcon(getIcon('zoom-out', 16, 'svgz')) 475 self.ui.act_canvas_zoom_100.setIcon(getIcon('zoom-original', 16, 'svgz')) 476 self.ui.act_settings_configure.setIcon(getIcon('configure', 16, 'svgz')) 477 self.ui.b_disk_add.setIcon(getIcon('list-add', 16, 'svgz')) 478 self.ui.b_disk_remove.setIcon(getIcon('list-remove', 16, 'svgz')) 479 self.ui.b_transport_play.setIcon(getIcon('media-playback-start', 16, 'svgz')) 480 self.ui.b_transport_stop.setIcon(getIcon('media-playback-stop', 16, 'svgz')) 481 self.ui.b_transport_backwards.setIcon(getIcon('media-seek-backward', 16, 'svgz')) 482 self.ui.b_transport_forwards.setIcon(getIcon('media-seek-forward', 16, 'svgz')) 483 self.ui.logs_clear.setIcon(getIcon('edit-clear', 16, 'svgz')) 484 self.ui.logs_save.setIcon(getIcon('document-save', 16, 'svgz')) 485 486 # ---------------------------------------------------------------------------------------------------- 487 # Connect actions to functions 488 489 self.ui.act_file_new.triggered.connect(self.slot_fileNew) 490 self.ui.act_file_open.triggered.connect(self.slot_fileOpen) 491 self.ui.act_file_save.triggered.connect(self.slot_fileSave) 492 self.ui.act_file_save_as.triggered.connect(self.slot_fileSaveAs) 493 494 self.ui.act_engine_start.triggered.connect(self.slot_engineStart) 495 self.ui.act_engine_stop.triggered.connect(self.slot_engineStop) 496 self.ui.act_engine_panic.triggered.connect(self.slot_pluginsDisable) 497 self.ui.act_engine_config.triggered.connect(self.slot_engineConfig) 498 499 self.ui.act_plugin_add.triggered.connect(self.slot_pluginAdd) 500 self.ui.act_plugin_add_jack.triggered.connect(self.slot_jackAppAdd) 501 self.ui.act_plugin_remove_all.triggered.connect(self.slot_confirmRemoveAll) 502 503 self.ui.act_plugins_enable.triggered.connect(self.slot_pluginsEnable) 504 self.ui.act_plugins_disable.triggered.connect(self.slot_pluginsDisable) 505 self.ui.act_plugins_volume100.triggered.connect(self.slot_pluginsVolume100) 506 self.ui.act_plugins_mute.triggered.connect(self.slot_pluginsMute) 507 self.ui.act_plugins_wet100.triggered.connect(self.slot_pluginsWet100) 508 self.ui.act_plugins_bypass.triggered.connect(self.slot_pluginsBypass) 509 self.ui.act_plugins_center.triggered.connect(self.slot_pluginsCenter) 510 self.ui.act_plugins_compact.triggered.connect(self.slot_pluginsCompact) 511 self.ui.act_plugins_expand.triggered.connect(self.slot_pluginsExpand) 512 513 self.ui.act_settings_show_toolbar.toggled.connect(self.slot_showToolbar) 514 self.ui.act_settings_show_meters.toggled.connect(self.slot_showCanvasMeters) 515 self.ui.act_settings_show_keyboard.toggled.connect(self.slot_showCanvasKeyboard) 516 self.ui.act_settings_show_side_panel.toggled.connect(self.slot_showSidePanel) 517 self.ui.act_settings_configure.triggered.connect(self.slot_configureCarla) 518 519 self.ui.act_help_about.triggered.connect(self.slot_aboutCarla) 520 self.ui.act_help_about_juce.triggered.connect(self.slot_aboutJuce) 521 self.ui.act_help_about_qt.triggered.connect(self.slot_aboutQt) 522 523 self.ui.cb_disk.currentIndexChanged.connect(self.slot_diskFolderChanged) 524 self.ui.b_disk_add.clicked.connect(self.slot_diskFolderAdd) 525 self.ui.b_disk_remove.clicked.connect(self.slot_diskFolderRemove) 526 self.ui.fileTreeView.doubleClicked.connect(self.slot_fileTreeDoubleClicked) 527 528 self.ui.b_transport_play.clicked.connect(self.slot_transportPlayPause) 529 self.ui.b_transport_stop.clicked.connect(self.slot_transportStop) 530 self.ui.b_transport_backwards.clicked.connect(self.slot_transportBackwards) 531 self.ui.b_transport_forwards.clicked.connect(self.slot_transportForwards) 532 self.ui.dsb_transport_bpm.valueChanged.connect(self.slot_transportBpmChanged) 533 self.ui.cb_transport_jack.clicked.connect(self.slot_transportJackEnabled) 534 self.ui.cb_transport_link.clicked.connect(self.slot_transportLinkEnabled) 535 536 self.ui.b_xruns.clicked.connect(self.slot_xrunClear) 537 538 self.ui.listWidget.customContextMenuRequested.connect(self.slot_showPluginActionsMenu) 539 540 self.ui.keyboard.noteOn.connect(self.slot_noteOn) 541 self.ui.keyboard.noteOff.connect(self.slot_noteOff) 542 543 self.ui.tabWidget.currentChanged.connect(self.slot_tabChanged) 544 545 if withCanvas: 546 self.ui.act_canvas_show_internal.triggered.connect(self.slot_canvasShowInternal) 547 self.ui.act_canvas_show_external.triggered.connect(self.slot_canvasShowExternal) 548 self.ui.act_canvas_arrange.triggered.connect(self.slot_canvasArrange) 549 self.ui.act_canvas_refresh.triggered.connect(self.slot_canvasRefresh) 550 self.ui.act_canvas_zoom_fit.triggered.connect(self.slot_canvasZoomFit) 551 self.ui.act_canvas_zoom_in.triggered.connect(self.slot_canvasZoomIn) 552 self.ui.act_canvas_zoom_out.triggered.connect(self.slot_canvasZoomOut) 553 self.ui.act_canvas_zoom_100.triggered.connect(self.slot_canvasZoomReset) 554 self.ui.act_canvas_save_image.triggered.connect(self.slot_canvasSaveImage) 555 self.ui.act_canvas_save_image_2x.triggered.connect(self.slot_canvasSaveImage) 556 self.ui.act_canvas_save_image_4x.triggered.connect(self.slot_canvasSaveImage) 557 self.ui.act_canvas_copy_clipboard.triggered.connect(self.slot_canvasCopyToClipboard) 558 self.ui.act_canvas_arrange.setEnabled(False) # TODO, later 559 self.ui.graphicsView.horizontalScrollBar().valueChanged.connect(self.slot_horizontalScrollBarChanged) 560 self.ui.graphicsView.verticalScrollBar().valueChanged.connect(self.slot_verticalScrollBarChanged) 561 self.ui.miniCanvasPreview.miniCanvasMoved.connect(self.slot_miniCanvasMoved) 562 self.scene.scaleChanged.connect(self.slot_canvasScaleChanged) 563 self.scene.pluginSelected.connect(self.slot_canvasPluginSelected) 564 self.scene.selectionChanged.connect(self.slot_canvasSelectionChanged) 565 566 self.SIGUSR1.connect(self.slot_handleSIGUSR1) 567 self.SIGTERM.connect(self.slot_handleSIGTERM) 568 569 host.EngineStartedCallback.connect(self.slot_handleEngineStartedCallback) 570 host.EngineStoppedCallback.connect(self.slot_handleEngineStoppedCallback) 571 host.TransportModeChangedCallback.connect(self.slot_handleTransportModeChangedCallback) 572 host.BufferSizeChangedCallback.connect(self.slot_handleBufferSizeChangedCallback) 573 host.SampleRateChangedCallback.connect(self.slot_handleSampleRateChangedCallback) 574 host.CancelableActionCallback.connect(self.slot_handleCancelableActionCallback) 575 host.ProjectLoadFinishedCallback.connect(self.slot_handleProjectLoadFinishedCallback) 576 577 host.PluginAddedCallback.connect(self.slot_handlePluginAddedCallback) 578 host.PluginRemovedCallback.connect(self.slot_handlePluginRemovedCallback) 579 host.ReloadAllCallback.connect(self.slot_handleReloadAllCallback) 580 581 host.NoteOnCallback.connect(self.slot_handleNoteOnCallback) 582 host.NoteOffCallback.connect(self.slot_handleNoteOffCallback) 583 584 host.UpdateCallback.connect(self.slot_handleUpdateCallback) 585 586 if withCanvas: 587 host.PatchbayClientAddedCallback.connect(self.slot_handlePatchbayClientAddedCallback) 588 host.PatchbayClientRemovedCallback.connect(self.slot_handlePatchbayClientRemovedCallback) 589 host.PatchbayClientRenamedCallback.connect(self.slot_handlePatchbayClientRenamedCallback) 590 host.PatchbayClientDataChangedCallback.connect(self.slot_handlePatchbayClientDataChangedCallback) 591 host.PatchbayClientPositionChangedCallback.connect(self.slot_handlePatchbayClientPositionChangedCallback) 592 host.PatchbayPortAddedCallback.connect(self.slot_handlePatchbayPortAddedCallback) 593 host.PatchbayPortRemovedCallback.connect(self.slot_handlePatchbayPortRemovedCallback) 594 host.PatchbayPortChangedCallback.connect(self.slot_handlePatchbayPortChangedCallback) 595 host.PatchbayPortGroupAddedCallback.connect(self.slot_handlePatchbayPortGroupAddedCallback) 596 host.PatchbayPortGroupRemovedCallback.connect(self.slot_handlePatchbayPortGroupRemovedCallback) 597 host.PatchbayPortGroupChangedCallback.connect(self.slot_handlePatchbayPortGroupChangedCallback) 598 host.PatchbayConnectionAddedCallback.connect(self.slot_handlePatchbayConnectionAddedCallback) 599 host.PatchbayConnectionRemovedCallback.connect(self.slot_handlePatchbayConnectionRemovedCallback) 600 601 host.NSMCallback.connect(self.slot_handleNSMCallback) 602 603 host.DebugCallback.connect(self.slot_handleDebugCallback) 604 host.InfoCallback.connect(self.slot_handleInfoCallback) 605 host.ErrorCallback.connect(self.slot_handleErrorCallback) 606 host.QuitCallback.connect(self.slot_handleQuitCallback) 607 host.InlineDisplayRedrawCallback.connect(self.slot_handleInlineDisplayRedrawCallback) 608 609 # ---------------------------------------------------------------------------------------------------- 610 # Final setup 611 612 self.ui.text_logs.clear() 613 self.slot_logButtonsState(False) 614 self.setProperWindowTitle() 615 616 # Disable non-supported features 617 features = gCarla.utils.get_supported_features() 618 619 if "link" not in features: 620 self.ui.cb_transport_link.setEnabled(False) 621 self.ui.cb_transport_link.setVisible(False) 622 623 if "juce" not in features: 624 self.ui.act_help_about_juce.setEnabled(False) 625 self.ui.act_help_about_juce.setVisible(False) 626 627 # Plugin needs to have timers always running so it receives messages 628 if self.host.isPlugin or self.host.isRemote: 629 self.startTimers() 630 631 # Qt needs this so it properly creates & resizes the canvas 632 self.ui.tabWidget.blockSignals(True) 633 self.ui.tabWidget.setCurrentIndex(1) 634 self.ui.tabWidget.setCurrentIndex(0) 635 self.ui.tabWidget.blockSignals(False) 636 637 # Start in patchbay tab if using forced patchbay mode 638 if host.processModeForced and host.processMode == ENGINE_PROCESS_MODE_PATCHBAY: 639 self.ui.tabWidget.setCurrentIndex(1) 640 641 # Load initial project file if set 642 if not (self.host.isControl or self.host.isPlugin): 643 projectFile = getInitialProjectFile(QApplication.instance()) 644 645 if projectFile: 646 self.loadProjectLater(projectFile) 647 648 # For NSM we wait for the open message 649 if NSM_URL and host.nsmOK: 650 host.nsm_ready(NSM_CALLBACK_INIT) 651 return 652 653 if not host.isControl: 654 QTimer.singleShot(0, self.slot_engineStart) 655 656 # -------------------------------------------------------------------------------------------------------- 657 # Manage visibility state, needed for NSM 658 659 def hideForNSM(self): 660 for pitem in reversed(self.fPluginList): 661 if pitem is None: 662 continue 663 pitem.getWidget().hideCustomUI() 664 self.hide() 665 666 def showIfNeeded(self): 667 if self.host.nsmOK: 668 self.ui.act_file_quit.setText(self.tr("Hide")) 669 QApplication.instance().setQuitOnLastWindowClosed(False) 670 else: 671 self.show() 672 673 # -------------------------------------------------------------------------------------------------------- 674 # Setup 675 676 def compactPlugin(self, pluginId): 677 if pluginId > self.fPluginCount: 678 return 679 680 pitem = self.fPluginList[pluginId] 681 682 if pitem is None: 683 return 684 685 pitem.recreateWidget(True) 686 687 def changePluginColor(self, pluginId, color, colorStr): 688 if pluginId > self.fPluginCount: 689 return 690 691 pitem = self.fPluginList[pluginId] 692 693 if pitem is None: 694 return 695 696 self.host.set_custom_data(pluginId, CUSTOM_DATA_TYPE_PROPERTY, "CarlaColor", colorStr) 697 pitem.recreateWidget(newColor = color) 698 699 def changePluginSkin(self, pluginId, skin): 700 if pluginId > self.fPluginCount: 701 return 702 703 pitem = self.fPluginList[pluginId] 704 705 if pitem is None: 706 return 707 708 self.host.set_custom_data(pluginId, CUSTOM_DATA_TYPE_PROPERTY, "CarlaSkin", skin) 709 if skin not in ("default","rncbc","presets","mpresets"): 710 pitem.recreateWidget(newSkin = skin, newColor = (255,255,255)) 711 else: 712 pitem.recreateWidget(newSkin = skin) 713 714 def findPluginInPatchbay(self, pluginId): 715 if pluginId > self.fPluginCount: 716 return 717 718 if self.fExternalPatchbay: 719 self.slot_canvasShowInternal() 720 721 if not patchcanvas.focusGroupUsingPluginId(pluginId): 722 name = self.host.get_plugin_info(pluginId)['name'] 723 if not patchcanvas.focusGroupUsingGroupName(name): 724 return 725 726 self.ui.tabWidget.setCurrentIndex(1) 727 728 def switchPlugins(self, pluginIdA, pluginIdB): 729 if pluginIdA == pluginIdB: 730 return 731 if pluginIdA < 0 or pluginIdB < 0: 732 return 733 if pluginIdA >= self.fPluginCount or pluginIdB >= self.fPluginCount: 734 return 735 736 self.host.switch_plugins(pluginIdA, pluginIdB) 737 738 itemA = self.fPluginList[pluginIdA] 739 compactA = itemA.isCompacted() 740 guiShownA = itemA.isGuiShown() 741 742 itemB = self.fPluginList[pluginIdB] 743 compactB = itemB.isCompacted() 744 guiShownB = itemB.isGuiShown() 745 746 itemA.setPluginId(pluginIdA) 747 itemA.recreateWidget2(compactB, guiShownB) 748 749 itemB.setPluginId(pluginIdB) 750 itemB.recreateWidget2(compactA, guiShownA) 751 752 if self.fWithCanvas: 753 self.slot_canvasRefresh() 754 755 def setLoadRDFsNeeded(self): 756 self.fLadspaRdfNeedsUpdate = True 757 758 def setProperWindowTitle(self): 759 title = self.fClientName 760 761 if self.fProjectFilename and not self.host.nsmOK: 762 title += " - %s" % os.path.basename(self.fProjectFilename) 763 if self.fSessionManagerName: 764 title += " (%s)" % self.fSessionManagerName 765 766 self.setWindowTitle(title) 767 768 def updateBufferSize(self, newBufferSize): 769 if self.fBufferSize == newBufferSize: 770 return 771 self.fBufferSize = newBufferSize 772 self.ui.cb_buffer_size.clear() 773 self.ui.cb_buffer_size.addItem(str(newBufferSize)) 774 self.ui.cb_buffer_size.setCurrentIndex(0) 775 776 def updateSampleRate(self, newSampleRate): 777 if self.fSampleRate == newSampleRate: 778 return 779 self.fSampleRate = newSampleRate 780 self.ui.cb_sample_rate.clear() 781 self.ui.cb_sample_rate.addItem(str(newSampleRate)) 782 self.ui.cb_sample_rate.setCurrentIndex(0) 783 self.refreshTransport(True) 784 785 # -------------------------------------------------------------------------------------------------------- 786 # Files 787 788 def loadProjectNow(self): 789 if not self.fProjectFilename: 790 return qCritical("ERROR: loading project without filename set") 791 if self.host.nsmOK and not os.path.exists(self.fProjectFilename): 792 return 793 794 self.projectLoadingStarted() 795 self.fIsProjectLoading = True 796 797 if not self.host.load_project(self.fProjectFilename): 798 self.fIsProjectLoading = False 799 self.projectLoadingFinished(True) 800 801 CustomMessageBox(self, QMessageBox.Critical, self.tr("Error"), self.tr("Failed to load project"), 802 self.host.get_last_error(), 803 QMessageBox.Ok, QMessageBox.Ok) 804 805 def loadProjectLater(self, filename): 806 self.fProjectFilename = QFileInfo(filename).absoluteFilePath() 807 self.setProperWindowTitle() 808 QTimer.singleShot(1, self.slot_loadProjectNow) 809 810 def saveProjectNow(self): 811 if not self.fProjectFilename: 812 return qCritical("ERROR: saving project without filename set") 813 814 if not self.host.save_project(self.fProjectFilename): 815 CustomMessageBox(self, QMessageBox.Critical, self.tr("Error"), self.tr("Failed to save project"), 816 self.host.get_last_error(), 817 QMessageBox.Ok, QMessageBox.Ok) 818 return 819 820 def projectLoadingStarted(self): 821 self.ui.rack.setEnabled(False) 822 self.ui.graphicsView.setEnabled(False) 823 824 def projectLoadingFinished(self, refreshCanvas): 825 self.ui.rack.setEnabled(True) 826 self.ui.graphicsView.setEnabled(True) 827 828 if self.fCustomStopAction == self.CUSTOM_ACTION_APP_CLOSE or not self.fWithCanvas: 829 return 830 831 if refreshCanvas and not self.loadExternalCanvasGroupPositionsIfNeeded(self.fProjectFilename): 832 QTimer.singleShot(1, self.slot_canvasRefresh) 833 834 def loadExternalCanvasGroupPositionsIfNeeded(self, filename): 835 extrafile = filename.rsplit(".",1)[0]+".json" 836 if not os.path.exists(extrafile): 837 return False 838 839 with open(filename, "r") as fh: 840 if "".join(fh.readlines(90)).find("<CARLA-PROJECT VERSION='2.0'>") < 0: 841 return False 842 843 with open(extrafile, "r") as fh: 844 try: 845 canvasdata = json.load(fh)['canvas'] 846 except: 847 return False 848 849 print("NOTICE: loading old-style canvas group positions via legacy json file") 850 patchcanvas.restoreGroupPositions(canvasdata) 851 QTimer.singleShot(1, self.slot_canvasRefresh) 852 return True 853 854 # -------------------------------------------------------------------------------------------------------- 855 # Files (menu actions) 856 857 @pyqtSlot() 858 def slot_fileNew(self): 859 if self.fPluginCount > 0 and QMessageBox.question(self, self.tr("New File"), 860 self.tr("Plugins that are currently loaded will be removed. Are you sure?"), 861 QMessageBox.Yes|QMessageBox.No) == QMessageBox.No: 862 return 863 864 self.pluginRemoveAll() 865 self.fProjectFilename = "" 866 self.setProperWindowTitle() 867 self.host.clear_project_filename() 868 869 @pyqtSlot() 870 def slot_fileOpen(self): 871 fileFilter = self.tr("Carla Project File (*.carxp);;Carla Preset File (*.carxs)") 872 filename, ok = QFileDialog.getOpenFileName(self, self.tr("Open Carla Project File"), self.fSavedSettings[CARLA_KEY_MAIN_PROJECT_FOLDER], filter=fileFilter) 873 874 # FIXME use ok value, test if it works as expected 875 if not filename: 876 return 877 878 newFile = True 879 880 if self.fPluginCount > 0: 881 ask = QMessageBox.question(self, self.tr("Question"), self.tr("There are some plugins loaded, do you want to remove them now?"), 882 QMessageBox.Yes | QMessageBox.No, QMessageBox.No) 883 newFile = (ask == QMessageBox.Yes) 884 885 if newFile: 886 self.pluginRemoveAll() 887 self.fProjectFilename = filename 888 self.setProperWindowTitle() 889 self.loadProjectNow() 890 else: 891 filenameOld = self.fProjectFilename 892 self.fProjectFilename = filename 893 self.loadProjectNow() 894 self.fProjectFilename = filenameOld 895 896 @pyqtSlot() 897 def slot_fileSave(self, saveAs=False): 898 if self.fProjectFilename and not saveAs: 899 return self.saveProjectNow() 900 901 fileFilter = self.tr("Carla Project File (*.carxp)") 902 filename, ok = QFileDialog.getSaveFileName(self, self.tr("Save Carla Project File"), self.fSavedSettings[CARLA_KEY_MAIN_PROJECT_FOLDER], filter=fileFilter) 903 904 # FIXME use ok value, test if it works as expected 905 if not filename: 906 return 907 908 if not filename.lower().endswith(".carxp"): 909 filename += ".carxp" 910 911 if self.fProjectFilename != filename: 912 self.fProjectFilename = filename 913 self.setProperWindowTitle() 914 915 self.saveProjectNow() 916 917 @pyqtSlot() 918 def slot_fileSaveAs(self): 919 self.slot_fileSave(True) 920 921 @pyqtSlot() 922 def slot_loadProjectNow(self): 923 self.loadProjectNow() 924 925 # -------------------------------------------------------------------------------------------------------- 926 # Engine (menu actions) 927 928 @pyqtSlot() 929 def slot_engineStart(self): 930 audioDriver = setEngineSettings(self.host) 931 firstInit = self.fFirstEngineInit 932 933 self.fFirstEngineInit = False 934 self.ui.text_logs.appendPlainText("======= Starting engine =======") 935 936 if self.host.engine_init(audioDriver, self.fClientName): 937 if firstInit and not (self.host.isControl or self.host.isPlugin): 938 settings = QSafeSettings() 939 lastBpm = settings.value("LastBPM", 120.0, float) 940 del settings 941 if lastBpm >= 20.0: 942 self.host.transport_bpm(lastBpm) 943 return 944 945 elif firstInit: 946 self.ui.text_logs.appendPlainText("Failed to start engine on first try, ignored") 947 return 948 949 audioError = self.host.get_last_error() 950 951 if audioError: 952 QMessageBox.critical(self, self.tr("Error"), self.tr("Could not connect to Audio backend '%s', possible reasons:\n%s" % (audioDriver, audioError))) 953 else: 954 QMessageBox.critical(self, self.tr("Error"), self.tr("Could not connect to Audio backend '%s'" % audioDriver)) 955 956 @pyqtSlot() 957 def slot_engineStop(self, forced = False): 958 self.ui.text_logs.appendPlainText("======= Stopping engine =======") 959 960 if self.fPluginCount == 0 or not self.host.is_engine_running(): 961 self.engineStopFinal() 962 return True 963 964 if not forced: 965 ask = QMessageBox.question(self, self.tr("Warning"), self.tr("There are still some plugins loaded, you need to remove them to stop the engine.\n" 966 "Do you want to do this now?"), 967 QMessageBox.Yes | QMessageBox.No, QMessageBox.No) 968 if ask != QMessageBox.Yes: 969 return False 970 971 return self.slot_engineStopTryAgain() 972 973 @pyqtSlot() 974 def slot_engineConfig(self): 975 dialog = RuntimeDriverSettingsW(self.fParentOrSelf, self.host) 976 977 if not dialog.exec_(): 978 return 979 980 audioDevice, bufferSize, sampleRate = dialog.getValues() 981 982 if self.host.is_engine_running(): 983 self.host.set_engine_buffer_size_and_sample_rate(bufferSize, sampleRate) 984 else: 985 self.host.set_engine_option(ENGINE_OPTION_AUDIO_DEVICE, 0, audioDevice) 986 self.host.set_engine_option(ENGINE_OPTION_AUDIO_BUFFER_SIZE, bufferSize, "") 987 self.host.set_engine_option(ENGINE_OPTION_AUDIO_SAMPLE_RATE, sampleRate, "") 988 989 @pyqtSlot() 990 def slot_engineStopTryAgain(self): 991 if self.host.is_engine_running() and not self.host.set_engine_about_to_close(): 992 QTimer.singleShot(0, self.slot_engineStopTryAgain) 993 return False 994 995 self.engineStopFinal() 996 return True 997 998 def engineStopFinal(self): 999 patchcanvas.handleAllPluginsRemoved() 1000 self.killTimers() 1001 1002 if self.fCustomStopAction == self.CUSTOM_ACTION_PROJECT_LOAD: 1003 self.removeAllPlugins() 1004 else: 1005 self.fProjectFilename = "" 1006 self.setProperWindowTitle() 1007 if self.fPluginCount != 0: 1008 self.fCurrentlyRemovingAllPlugins = True 1009 self.projectLoadingStarted() 1010 1011 if self.host.is_engine_running() and not self.host.remove_all_plugins(): 1012 self.ui.text_logs.appendPlainText("Failed to remove all plugins, error was:") 1013 self.ui.text_logs.appendPlainText(self.host.get_last_error()) 1014 1015 if not self.host.engine_close(): 1016 self.ui.text_logs.appendPlainText("Failed to stop engine, error was:") 1017 self.ui.text_logs.appendPlainText(self.host.get_last_error()) 1018 1019 if self.fCustomStopAction == self.CUSTOM_ACTION_APP_CLOSE: 1020 self.close() 1021 elif self.fCustomStopAction == self.CUSTOM_ACTION_PROJECT_LOAD: 1022 self.slot_engineStart() 1023 self.loadProjectNow() 1024 self.host.nsm_ready(NSM_CALLBACK_OPEN) 1025 1026 self.fCustomStopAction = self.CUSTOM_ACTION_NONE 1027 1028 # -------------------------------------------------------------------------------------------------------- 1029 # Engine (host callbacks) 1030 1031 @pyqtSlot(int, int, int, int, float, str) 1032 def slot_handleEngineStartedCallback(self, pluginCount, processMode, transportMode, bufferSize, sampleRate, driverName): 1033 self.ui.menu_PluginMacros.setEnabled(True) 1034 self.ui.menu_Canvas.setEnabled(True) 1035 self.ui.w_transport.setEnabled(True) 1036 1037 self.ui.act_canvas_show_internal.blockSignals(True) 1038 self.ui.act_canvas_show_external.blockSignals(True) 1039 1040 if processMode == ENGINE_PROCESS_MODE_PATCHBAY and not self.host.isPlugin: 1041 self.ui.act_canvas_show_internal.setChecked(True) 1042 self.ui.act_canvas_show_internal.setVisible(True) 1043 self.ui.act_canvas_show_external.setChecked(False) 1044 self.ui.act_canvas_show_external.setVisible(True) 1045 self.fExternalPatchbay = False 1046 else: 1047 self.ui.act_canvas_show_internal.setChecked(False) 1048 self.ui.act_canvas_show_internal.setVisible(False) 1049 self.ui.act_canvas_show_external.setChecked(True) 1050 self.ui.act_canvas_show_external.setVisible(False) 1051 self.fExternalPatchbay = not self.host.isPlugin 1052 1053 self.ui.act_canvas_show_internal.blockSignals(False) 1054 self.ui.act_canvas_show_external.blockSignals(False) 1055 1056 if not (self.host.isControl or self.host.isPlugin): 1057 canSave = (self.fProjectFilename and os.path.exists(self.fProjectFilename)) or not self.fSessionManagerName 1058 self.ui.act_file_save.setEnabled(canSave) 1059 self.ui.act_engine_start.setEnabled(False) 1060 self.ui.act_engine_stop.setEnabled(True) 1061 1062 if not self.host.isPlugin: 1063 self.enableTransport(transportMode != ENGINE_TRANSPORT_MODE_DISABLED) 1064 1065 if self.host.isPlugin or not self.fSessionManagerName: 1066 self.ui.act_file_open.setEnabled(True) 1067 self.ui.act_file_save_as.setEnabled(True) 1068 1069 self.ui.cb_transport_jack.setChecked(transportMode == ENGINE_TRANSPORT_MODE_JACK) 1070 self.ui.cb_transport_jack.setEnabled(driverName == "JACK" and processMode != ENGINE_PROCESS_MODE_MULTIPLE_CLIENTS) 1071 1072 if self.ui.cb_transport_link.isEnabled(): 1073 self.ui.cb_transport_link.setChecked(":link:" in self.host.transportExtra) 1074 1075 self.updateBufferSize(bufferSize) 1076 self.updateSampleRate(int(sampleRate)) 1077 self.refreshRuntimeInfo(0.0, 0) 1078 self.startTimers() 1079 1080 self.ui.text_logs.appendPlainText("======= Engine started ========") 1081 self.ui.text_logs.appendPlainText("Carla engine started, details:") 1082 self.ui.text_logs.appendPlainText(" Driver name: %s" % driverName) 1083 self.ui.text_logs.appendPlainText(" Sample rate: %i" % int(sampleRate)) 1084 self.ui.text_logs.appendPlainText(" Process mode: %s" % processMode2Str(processMode)) 1085 1086 @pyqtSlot() 1087 def slot_handleEngineStoppedCallback(self): 1088 self.ui.text_logs.appendPlainText("======= Engine stopped ========") 1089 1090 if self.fWithCanvas: 1091 patchcanvas.clear() 1092 1093 self.killTimers() 1094 1095 # just in case 1096 self.removeAllPlugins() 1097 self.refreshRuntimeInfo(0.0, 0) 1098 1099 self.ui.menu_PluginMacros.setEnabled(False) 1100 self.ui.menu_Canvas.setEnabled(False) 1101 self.ui.w_transport.setEnabled(False) 1102 1103 if not (self.host.isControl or self.host.isPlugin): 1104 self.ui.act_file_save.setEnabled(False) 1105 self.ui.act_engine_start.setEnabled(True) 1106 self.ui.act_engine_stop.setEnabled(False) 1107 1108 if self.host.isPlugin or not self.fSessionManagerName: 1109 self.ui.act_file_open.setEnabled(False) 1110 self.ui.act_file_save_as.setEnabled(False) 1111 1112 @pyqtSlot(int, str) 1113 def slot_handleTransportModeChangedCallback(self, transportMode, transportExtra): 1114 self.enableTransport(transportMode != ENGINE_TRANSPORT_MODE_DISABLED) 1115 1116 self.ui.cb_transport_jack.setChecked(transportMode == ENGINE_TRANSPORT_MODE_JACK) 1117 self.ui.cb_transport_link.setChecked(":link:" in transportExtra) 1118 1119 @pyqtSlot(int) 1120 def slot_handleBufferSizeChangedCallback(self, newBufferSize): 1121 self.updateBufferSize(newBufferSize) 1122 1123 @pyqtSlot(float) 1124 def slot_handleSampleRateChangedCallback(self, newSampleRate): 1125 self.updateSampleRate(int(newSampleRate)) 1126 1127 @pyqtSlot(int, bool, str) 1128 def slot_handleCancelableActionCallback(self, pluginId, started, action): 1129 if self.fCancelableActionBox is not None: 1130 self.fCancelableActionBox.close() 1131 1132 if started: 1133 self.fCancelableActionBox = QMessageBox(self) 1134 self.fCancelableActionBox.setIcon(QMessageBox.Information) 1135 self.fCancelableActionBox.setWindowTitle(self.tr("Action in progress")) 1136 self.fCancelableActionBox.setText(action) 1137 self.fCancelableActionBox.setInformativeText(self.tr("An action is in progress, please wait...")) 1138 self.fCancelableActionBox.setStandardButtons(QMessageBox.Cancel) 1139 self.fCancelableActionBox.setDefaultButton(QMessageBox.Cancel) 1140 self.fCancelableActionBox.buttonClicked.connect(self.slot_canlableActionBoxClicked) 1141 self.fCancelableActionBox.show() 1142 1143 else: 1144 self.fCancelableActionBox = None 1145 1146 @pyqtSlot() 1147 def slot_canlableActionBoxClicked(self): 1148 self.host.cancel_engine_action() 1149 1150 @pyqtSlot() 1151 def slot_handleProjectLoadFinishedCallback(self): 1152 self.fIsProjectLoading = False 1153 self.projectLoadingFinished(False) 1154 1155 # -------------------------------------------------------------------------------------------------------- 1156 # Plugins 1157 1158 def removeAllPlugins(self): 1159 self.ui.act_plugin_remove_all.setEnabled(False) 1160 patchcanvas.handleAllPluginsRemoved() 1161 1162 while self.ui.listWidget.takeItem(0): 1163 pass 1164 1165 self.clearSideStuff() 1166 1167 for pitem in self.fPluginList: 1168 if pitem is None: 1169 continue 1170 1171 pitem.close() 1172 del pitem 1173 1174 self.fPluginCount = 0 1175 self.fPluginList = [] 1176 1177 # -------------------------------------------------------------------------------------------------------- 1178 # Plugins (menu actions) 1179 1180 def showAddPluginDialog(self): 1181 if self.fPluginDatabaseDialog is None: 1182 self.fPluginDatabaseDialog = PluginDatabaseW(self.fParentOrSelf, self.host, 1183 self.fSavedSettings[CARLA_KEY_MAIN_SYSTEM_ICONS]) 1184 dialog = self.fPluginDatabaseDialog 1185 1186 ret = dialog.exec_() 1187 1188 if dialog.fFavoritePluginsChanged: 1189 self.fFavoritePlugins = dialog.fFavoritePlugins 1190 1191 if not ret: 1192 return 1193 1194 if not self.host.is_engine_running(): 1195 QMessageBox.warning(self, self.tr("Warning"), self.tr("Cannot add new plugins while engine is stopped")) 1196 return 1197 1198 btype = dialog.fRetPlugin['build'] 1199 ptype = dialog.fRetPlugin['type'] 1200 filename = dialog.fRetPlugin['filename'] 1201 label = dialog.fRetPlugin['label'] 1202 uniqueId = dialog.fRetPlugin['uniqueId'] 1203 extraPtr = self.getExtraPtr(dialog.fRetPlugin) 1204 1205 return (btype, ptype, filename, label, uniqueId, extraPtr) 1206 1207 def showAddJackAppDialog(self): 1208 dialog = JackApplicationW(self.fParentOrSelf, self.fProjectFilename) 1209 1210 if not dialog.exec_(): 1211 return 1212 1213 if not self.host.is_engine_running(): 1214 QMessageBox.warning(self, self.tr("Warning"), self.tr("Cannot add new plugins while engine is stopped")) 1215 return 1216 1217 return dialog.getCommandAndFlags() 1218 1219 @pyqtSlot() 1220 def slot_favoritePluginAdd(self): 1221 plugin = self.sender().data() 1222 1223 if plugin is None: 1224 return 1225 1226 if not self.host.add_plugin(plugin['build'], plugin['type'], plugin['filename'], None, 1227 plugin['label'], plugin['uniqueId'], None, PLUGIN_OPTIONS_NULL): 1228 # remove plugin from favorites 1229 try: 1230 self.fFavoritePlugins.remove(plugin) 1231 except ValueError: 1232 pass 1233 else: 1234 settingsDBf = QSafeSettings("falkTX", "CarlaDatabase2") 1235 settingsDBf.setValue("PluginDatabase/Favorites", self.fFavoritePlugins) 1236 settingsDBf.sync() 1237 del settingsDBf 1238 1239 CustomMessageBox(self, 1240 QMessageBox.Critical, 1241 self.tr("Error"), 1242 self.tr("Failed to load plugin"), 1243 self.host.get_last_error(), QMessageBox.Ok, QMessageBox.Ok) 1244 1245 @pyqtSlot() 1246 def slot_showPluginActionsMenu(self): 1247 menu = QMenu(self) 1248 1249 menu.addSection("Plugins") 1250 menu.addAction(self.ui.act_plugin_add) 1251 1252 if len(self.fFavoritePlugins) != 0: 1253 fmenu = QMenu("Add from favorites", self) 1254 for p in self.fFavoritePlugins: 1255 act = fmenu.addAction(p['name']) 1256 act.setData(p) 1257 act.triggered.connect(self.slot_favoritePluginAdd) 1258 menu.addMenu(fmenu) 1259 1260 menu.addAction(self.ui.act_plugin_remove_all) 1261 1262 menu.addSection("All plugins (macros)") 1263 menu.addAction(self.ui.act_plugins_enable) 1264 menu.addAction(self.ui.act_plugins_disable) 1265 menu.addSeparator() 1266 menu.addAction(self.ui.act_plugins_volume100) 1267 menu.addAction(self.ui.act_plugins_mute) 1268 menu.addSeparator() 1269 menu.addAction(self.ui.act_plugins_wet100) 1270 menu.addAction(self.ui.act_plugins_bypass) 1271 menu.addSeparator() 1272 menu.addAction(self.ui.act_plugins_center) 1273 menu.addSeparator() 1274 menu.addAction(self.ui.act_plugins_compact) 1275 menu.addAction(self.ui.act_plugins_expand) 1276 1277 menu.exec_(QCursor.pos()) 1278 1279 @pyqtSlot() 1280 def slot_pluginAdd(self): 1281 data = self.showAddPluginDialog() 1282 1283 if data is None: 1284 return 1285 1286 btype, ptype, filename, label, uniqueId, extraPtr = data 1287 1288 if not self.host.add_plugin(btype, ptype, filename, None, label, uniqueId, extraPtr, PLUGIN_OPTIONS_NULL): 1289 CustomMessageBox(self, QMessageBox.Critical, self.tr("Error"), self.tr("Failed to load plugin"), 1290 self.host.get_last_error(), QMessageBox.Ok, QMessageBox.Ok) 1291 1292 @pyqtSlot() 1293 def slot_confirmRemoveAll(self): 1294 if self.fPluginCount == 0: 1295 return 1296 1297 if QMessageBox.question(self, self.tr("Remove All"), 1298 self.tr("Are you sure you want to remove all plugins?"), 1299 QMessageBox.Yes|QMessageBox.No) == QMessageBox.No: 1300 return 1301 1302 self.pluginRemoveAll() 1303 1304 def pluginRemoveAll(self): 1305 if self.fPluginCount == 0: 1306 return 1307 1308 self.fCurrentlyRemovingAllPlugins = True 1309 self.projectLoadingStarted() 1310 1311 if not self.host.remove_all_plugins(): 1312 self.projectLoadingFinished(True) 1313 self.fCurrentlyRemovingAllPlugins = False 1314 CustomMessageBox(self, QMessageBox.Warning, self.tr("Error"), self.tr("Operation failed"), 1315 self.host.get_last_error(), QMessageBox.Ok, QMessageBox.Ok) 1316 1317 @pyqtSlot() 1318 def slot_jackAppAdd(self): 1319 data = self.showAddJackAppDialog() 1320 1321 if data is None: 1322 return 1323 1324 filename, name, label = data 1325 1326 if not filename: 1327 CustomMessageBox(self, QMessageBox.Critical, self.tr("Error"), self.tr("Cannot add jack application"), 1328 self.tr("command is empty"), QMessageBox.Ok, QMessageBox.Ok) 1329 return 1330 1331 if not self.host.add_plugin(BINARY_NATIVE, PLUGIN_JACK, filename, name, label, 0, None, PLUGIN_OPTIONS_NULL): 1332 CustomMessageBox(self, QMessageBox.Critical, self.tr("Error"), self.tr("Failed to load plugin"), 1333 self.host.get_last_error(), QMessageBox.Ok, QMessageBox.Ok) 1334 1335 # -------------------------------------------------------------------------------------------------------- 1336 # Plugins (macros) 1337 1338 @pyqtSlot() 1339 def slot_pluginsEnable(self): 1340 if not self.host.is_engine_running(): 1341 return 1342 1343 for pitem in self.fPluginList: 1344 if pitem is None: 1345 break 1346 1347 pitem.getWidget().setActive(True, True, True) 1348 1349 @pyqtSlot() 1350 def slot_pluginsDisable(self): 1351 if not self.host.is_engine_running(): 1352 return 1353 1354 for pitem in self.fPluginList: 1355 if pitem is None: 1356 break 1357 1358 pitem.getWidget().setActive(False, True, True) 1359 1360 @pyqtSlot() 1361 def slot_pluginsVolume100(self): 1362 if not self.host.is_engine_running(): 1363 return 1364 1365 for pitem in self.fPluginList: 1366 if pitem is None: 1367 break 1368 1369 pitem.getWidget().setInternalParameter(PLUGIN_CAN_VOLUME, 1.0) 1370 1371 @pyqtSlot() 1372 def slot_pluginsMute(self): 1373 if not self.host.is_engine_running(): 1374 return 1375 1376 for pitem in self.fPluginList: 1377 if pitem is None: 1378 break 1379 1380 pitem.getWidget().setInternalParameter(PLUGIN_CAN_VOLUME, 0.0) 1381 1382 @pyqtSlot() 1383 def slot_pluginsWet100(self): 1384 if not self.host.is_engine_running(): 1385 return 1386 1387 for pitem in self.fPluginList: 1388 if pitem is None: 1389 break 1390 1391 pitem.getWidget().setInternalParameter(PLUGIN_CAN_DRYWET, 1.0) 1392 1393 @pyqtSlot() 1394 def slot_pluginsBypass(self): 1395 if not self.host.is_engine_running(): 1396 return 1397 1398 for pitem in self.fPluginList: 1399 if pitem is None: 1400 break 1401 1402 pitem.getWidget().setInternalParameter(PLUGIN_CAN_DRYWET, 0.0) 1403 1404 @pyqtSlot() 1405 def slot_pluginsCenter(self): 1406 if not self.host.is_engine_running(): 1407 return 1408 1409 for pitem in self.fPluginList: 1410 if pitem is None: 1411 break 1412 1413 pitem.getWidget().setInternalParameter(PARAMETER_BALANCE_LEFT, -1.0) 1414 pitem.getWidget().setInternalParameter(PARAMETER_BALANCE_RIGHT, 1.0) 1415 pitem.getWidget().setInternalParameter(PARAMETER_PANNING, 0.0) 1416 1417 @pyqtSlot() 1418 def slot_pluginsCompact(self): 1419 for pitem in self.fPluginList: 1420 if pitem is None: 1421 break 1422 pitem.compact() 1423 1424 @pyqtSlot() 1425 def slot_pluginsExpand(self): 1426 for pitem in self.fPluginList: 1427 if pitem is None: 1428 break 1429 pitem.expand() 1430 1431 # -------------------------------------------------------------------------------------------------------- 1432 # Plugins (host callbacks) 1433 1434 @pyqtSlot(int, str) 1435 def slot_handlePluginAddedCallback(self, pluginId, pluginName): 1436 if pluginId != self.fPluginCount: 1437 print("ERROR: pluginAdded mismatch Id:", pluginId, self.fPluginCount) 1438 pitem = self.getPluginItem(pluginId) 1439 pitem.recreateWidget() 1440 return 1441 1442 pitem = self.ui.listWidget.createItem(pluginId, self.fSavedSettings[CARLA_KEY_MAIN_CLASSIC_SKIN]) 1443 self.fPluginList.append(pitem) 1444 self.fPluginCount += 1 1445 1446 self.ui.act_plugin_remove_all.setEnabled(self.fPluginCount > 0) 1447 1448 @pyqtSlot(int) 1449 def slot_handlePluginRemovedCallback(self, pluginId): 1450 if self.fWithCanvas: 1451 patchcanvas.handlePluginRemoved(pluginId) 1452 1453 if pluginId in self.fSelectedPlugins: 1454 self.clearSideStuff() 1455 1456 if self.fPluginCount == 0: 1457 return 1458 1459 pitem = self.getPluginItem(pluginId) 1460 1461 self.fPluginCount -= 1 1462 self.fPluginList.pop(pluginId) 1463 self.ui.listWidget.takeItem(pluginId) 1464 1465 if pitem is not None: 1466 pitem.close() 1467 del pitem 1468 1469 if self.fPluginCount == 0: 1470 self.ui.act_plugin_remove_all.setEnabled(False) 1471 if self.fCurrentlyRemovingAllPlugins: 1472 self.fCurrentlyRemovingAllPlugins = False 1473 self.projectLoadingFinished(False) 1474 return 1475 1476 # push all plugins 1 slot back 1477 for i in range(pluginId, self.fPluginCount): 1478 pitem = self.fPluginList[i] 1479 pitem.setPluginId(i) 1480 1481 self.ui.act_plugin_remove_all.setEnabled(True) 1482 1483 # -------------------------------------------------------------------------------------------------------- 1484 # Canvas 1485 1486 def clearSideStuff(self): 1487 if self.fWithCanvas: 1488 self.scene.clearSelection() 1489 1490 self.fSelectedPlugins = [] 1491 1492 self.ui.keyboard.allNotesOff(False) 1493 self.ui.scrollArea.setEnabled(False) 1494 1495 self.fPeaksCleared = True 1496 self.ui.peak_in.displayMeter(1, 0.0, True) 1497 self.ui.peak_in.displayMeter(2, 0.0, True) 1498 self.ui.peak_out.displayMeter(1, 0.0, True) 1499 self.ui.peak_out.displayMeter(2, 0.0, True) 1500 1501 def setupCanvas(self): 1502 pOptions = patchcanvas.options_t() 1503 pOptions.theme_name = self.fSavedSettings[CARLA_KEY_CANVAS_THEME] 1504 pOptions.auto_hide_groups = self.fSavedSettings[CARLA_KEY_CANVAS_AUTO_HIDE_GROUPS] 1505 pOptions.auto_select_items = self.fSavedSettings[CARLA_KEY_CANVAS_AUTO_SELECT_ITEMS] 1506 pOptions.use_bezier_lines = self.fSavedSettings[CARLA_KEY_CANVAS_USE_BEZIER_LINES] 1507 pOptions.antialiasing = self.fSavedSettings[CARLA_KEY_CANVAS_ANTIALIASING] 1508 pOptions.inline_displays = self.fSavedSettings[CARLA_KEY_CANVAS_INLINE_DISPLAYS] 1509 1510 if self.fSavedSettings[CARLA_KEY_CANVAS_FANCY_EYE_CANDY]: 1511 pOptions.eyecandy = patchcanvas.EYECANDY_FULL 1512 elif self.fSavedSettings[CARLA_KEY_CANVAS_EYE_CANDY]: 1513 pOptions.eyecandy = patchcanvas.EYECANDY_SMALL 1514 else: 1515 pOptions.eyecandy = patchcanvas.EYECANDY_NONE 1516 1517 pFeatures = patchcanvas.features_t() 1518 pFeatures.group_info = False 1519 pFeatures.group_rename = False 1520 pFeatures.port_info = False 1521 pFeatures.port_rename = False 1522 pFeatures.handle_group_pos = False 1523 1524 patchcanvas.setOptions(pOptions) 1525 patchcanvas.setFeatures(pFeatures) 1526 patchcanvas.init("Carla2", self.scene, canvasCallback, False) 1527 1528 tryCanvasSize = self.fSavedSettings[CARLA_KEY_CANVAS_SIZE].split("x") 1529 1530 if len(tryCanvasSize) == 2 and tryCanvasSize[0].isdigit() and tryCanvasSize[1].isdigit(): 1531 self.fCanvasWidth = int(tryCanvasSize[0]) 1532 self.fCanvasHeight = int(tryCanvasSize[1]) 1533 else: 1534 self.fCanvasWidth = CARLA_DEFAULT_CANVAS_SIZE_WIDTH 1535 self.fCanvasHeight = CARLA_DEFAULT_CANVAS_SIZE_HEIGHT 1536 1537 patchcanvas.setCanvasSize(0, 0, self.fCanvasWidth, self.fCanvasHeight) 1538 patchcanvas.setInitialPos(self.fCanvasWidth / 2, self.fCanvasHeight / 2) 1539 self.ui.graphicsView.setSceneRect(0, 0, self.fCanvasWidth, self.fCanvasHeight) 1540 1541 self.ui.miniCanvasPreview.setViewTheme(patchcanvas.canvas.theme.canvas_bg, patchcanvas.canvas.theme.rubberband_brush, patchcanvas.canvas.theme.rubberband_pen.color()) 1542 self.ui.miniCanvasPreview.init(self.scene, self.fCanvasWidth, self.fCanvasHeight, self.fSavedSettings[CARLA_KEY_CUSTOM_PAINTING]) 1543 1544 if self.fSavedSettings[CARLA_KEY_CANVAS_ANTIALIASING] != patchcanvas.ANTIALIASING_NONE: 1545 self.ui.graphicsView.setRenderHint(QPainter.Antialiasing, True) 1546 1547 fullAA = self.fSavedSettings[CARLA_KEY_CANVAS_ANTIALIASING] == patchcanvas.ANTIALIASING_FULL 1548 self.ui.graphicsView.setRenderHint(QPainter.SmoothPixmapTransform, fullAA) 1549 self.ui.graphicsView.setRenderHint(QPainter.TextAntialiasing, fullAA) 1550 1551 if self.fSavedSettings[CARLA_KEY_CANVAS_USE_OPENGL] and hasGL: 1552 self.ui.graphicsView.setRenderHint(QPainter.HighQualityAntialiasing, self.fSavedSettings[CARLA_KEY_CANVAS_HQ_ANTIALIASING]) 1553 1554 else: 1555 self.ui.graphicsView.setRenderHint(QPainter.Antialiasing, False) 1556 1557 if self.fSavedSettings[CARLA_KEY_CANVAS_FULL_REPAINTS]: 1558 self.ui.graphicsView.setViewportUpdateMode(QGraphicsView.FullViewportUpdate) 1559 else: 1560 self.ui.graphicsView.setViewportUpdateMode(QGraphicsView.MinimalViewportUpdate) 1561 1562 def updateCanvasInitialPos(self): 1563 x = self.ui.graphicsView.horizontalScrollBar().value() + self.width()/4 1564 y = self.ui.graphicsView.verticalScrollBar().value() + self.height()/4 1565 patchcanvas.setInitialPos(x, y) 1566 1567 def updateMiniCanvasLater(self): 1568 QTimer.singleShot(self.fMiniCanvasUpdateTimeout, self.ui.miniCanvasPreview.update) 1569 1570 # -------------------------------------------------------------------------------------------------------- 1571 # Canvas (menu actions) 1572 1573 @pyqtSlot() 1574 def slot_canvasShowInternal(self): 1575 self.fExternalPatchbay = False 1576 self.ui.act_canvas_show_internal.blockSignals(True) 1577 self.ui.act_canvas_show_external.blockSignals(True) 1578 self.ui.act_canvas_show_internal.setChecked(True) 1579 self.ui.act_canvas_show_external.setChecked(False) 1580 self.ui.act_canvas_show_internal.blockSignals(False) 1581 self.ui.act_canvas_show_external.blockSignals(False) 1582 self.slot_canvasRefresh() 1583 1584 @pyqtSlot() 1585 def slot_canvasShowExternal(self): 1586 self.fExternalPatchbay = True 1587 self.ui.act_canvas_show_internal.blockSignals(True) 1588 self.ui.act_canvas_show_external.blockSignals(True) 1589 self.ui.act_canvas_show_internal.setChecked(False) 1590 self.ui.act_canvas_show_external.setChecked(True) 1591 self.ui.act_canvas_show_internal.blockSignals(False) 1592 self.ui.act_canvas_show_external.blockSignals(False) 1593 self.slot_canvasRefresh() 1594 1595 @pyqtSlot() 1596 def slot_canvasArrange(self): 1597 patchcanvas.arrange() 1598 1599 @pyqtSlot() 1600 def slot_canvasRefresh(self): 1601 patchcanvas.clear() 1602 1603 if self.host.processMode == ENGINE_PROCESS_MODE_CONTINUOUS_RACK and self.host.isPlugin: 1604 return 1605 1606 if self.host.is_engine_running(): 1607 self.host.patchbay_refresh(self.fExternalPatchbay) 1608 1609 self.updateMiniCanvasLater() 1610 1611 @pyqtSlot() 1612 def slot_canvasZoomFit(self): 1613 self.scene.zoom_fit() 1614 1615 @pyqtSlot() 1616 def slot_canvasZoomIn(self): 1617 self.scene.zoom_in() 1618 1619 @pyqtSlot() 1620 def slot_canvasZoomOut(self): 1621 self.scene.zoom_out() 1622 1623 @pyqtSlot() 1624 def slot_canvasZoomReset(self): 1625 self.scene.zoom_reset() 1626 1627 def _canvasImageRender(self, zoom = 1.0): 1628 image = QImage(self.scene.width()*zoom, self.scene.height()*zoom, QImage.Format_RGB32) 1629 painter = QPainter(image) 1630 painter.save() 1631 painter.setRenderHints(painter.renderHints() | QPainter.Antialiasing | QPainter.TextAntialiasing) 1632 self.scene.clearSelection() 1633 self.scene.render(painter) 1634 painter.restore() 1635 del painter 1636 return image 1637 1638 def _canvasImageWrite(self, iw: QImageWriter, imgFormat: bytes, image: QImage): 1639 iw.setFormat(imgFormat) 1640 iw.setCompression(-1) 1641 if QT_VERSION >= 0x50500: 1642 iw.setOptimizedWrite(True) 1643 iw.write(image) 1644 1645 @pyqtSlot() 1646 def slot_canvasSaveImage(self): 1647 if self.fProjectFilename: 1648 dir = QFileInfo(self.fProjectFilename).absoluteDir().absolutePath() 1649 else: 1650 dir = self.fSavedSettings[CARLA_KEY_MAIN_PROJECT_FOLDER] 1651 1652 fileDialog = QFileDialog(self) 1653 fileDialog.setAcceptMode(QFileDialog.AcceptSave) 1654 fileDialog.setDirectory(dir) 1655 fileDialog.setFileMode(QFileDialog.AnyFile) 1656 fileDialog.setMimeTypeFilters(("image/png", "image/jpeg")) 1657 fileDialog.setNameFilter(self.tr("Images (*.png *.jpg)")) 1658 fileDialog.setOptions(QFileDialog.DontUseCustomDirectoryIcons) 1659 fileDialog.setWindowTitle(self.tr("Save Image")) 1660 1661 ok = fileDialog.exec_() 1662 1663 if not ok: 1664 return 1665 1666 newPath = fileDialog.selectedFiles() 1667 1668 if len(newPath) != 1: 1669 return 1670 1671 newPath = newPath[0] 1672 1673 if QT_VERSION >= 0x50900: 1674 if fileDialog.selectedMimeTypeFilter() == "image/jpeg": 1675 imgFormat = b"JPG" 1676 else: 1677 imgFormat = b"PNG" 1678 else: 1679 if newPath.lower().endswith((".jpg", ".jpeg")): 1680 imgFormat = b"JPG" 1681 else: 1682 imgFormat = b"PNG" 1683 1684 sender = self.sender() 1685 if sender == self.ui.act_canvas_save_image_2x: 1686 zoom = 2.0 1687 elif sender == self.ui.act_canvas_save_image_4x: 1688 zoom = 4.0 1689 else: 1690 zoom = 1.0 1691 1692 image = self._canvasImageRender(zoom) 1693 iw = QImageWriter(newPath) 1694 self._canvasImageWrite(iw, imgFormat, image) 1695 1696 @pyqtSlot() 1697 def slot_canvasCopyToClipboard(self): 1698 buffer = QBuffer() 1699 buffer.open(QIODevice.WriteOnly) 1700 1701 image = self._canvasImageRender() 1702 iw = QImageWriter(buffer, b"PNG") 1703 self._canvasImageWrite(iw, b"PNG", image) 1704 1705 buffer.close() 1706 1707 mimeData = QMimeData() 1708 mimeData.setData("image/png", buffer.buffer()); 1709 1710 QApplication.clipboard().setMimeData(mimeData) 1711 1712 # -------------------------------------------------------------------------------------------------------- 1713 # Canvas (canvas callbacks) 1714 1715 @pyqtSlot() 1716 def slot_canvasSelectionChanged(self): 1717 self.updateMiniCanvasLater() 1718 1719 @pyqtSlot(float) 1720 def slot_canvasScaleChanged(self, scale): 1721 self.ui.miniCanvasPreview.setViewScale(scale) 1722 1723 @pyqtSlot(list) 1724 def slot_canvasPluginSelected(self, pluginList): 1725 self.ui.keyboard.allNotesOff(False) 1726 self.ui.scrollArea.setEnabled(len(pluginList) != 0) # and self.fPluginCount > 0 1727 self.fSelectedPlugins = pluginList 1728 1729 # -------------------------------------------------------------------------------------------------------- 1730 # Canvas (host callbacks) 1731 1732 @pyqtSlot(int, int, int, str) 1733 def slot_handlePatchbayClientAddedCallback(self, clientId, clientIcon, pluginId, clientName): 1734 pcSplit = patchcanvas.SPLIT_UNDEF 1735 pcIcon = patchcanvas.ICON_APPLICATION 1736 1737 if clientIcon == PATCHBAY_ICON_PLUGIN: 1738 pcIcon = patchcanvas.ICON_PLUGIN 1739 if clientIcon == PATCHBAY_ICON_HARDWARE: 1740 pcIcon = patchcanvas.ICON_HARDWARE 1741 elif clientIcon == PATCHBAY_ICON_CARLA: 1742 pass 1743 elif clientIcon == PATCHBAY_ICON_DISTRHO: 1744 pcIcon = patchcanvas.ICON_DISTRHO 1745 elif clientIcon == PATCHBAY_ICON_FILE: 1746 pcIcon = patchcanvas.ICON_FILE 1747 1748 patchcanvas.addGroup(clientId, clientName, pcSplit, pcIcon) 1749 1750 self.updateMiniCanvasLater() 1751 1752 if pluginId < 0: 1753 return 1754 if pluginId >= self.fPluginCount and pluginId != MAIN_CARLA_PLUGIN_ID: 1755 print("Error mapping plugin to canvas client:", clientName) 1756 return 1757 1758 if pluginId == MAIN_CARLA_PLUGIN_ID: 1759 hasCustomUI = False 1760 hasInlineDisplay = False 1761 else: 1762 hints = self.host.get_plugin_info(pluginId)['hints'] 1763 hasCustomUI = bool(hints & PLUGIN_HAS_CUSTOM_UI) 1764 hasInlineDisplay = bool(hints & PLUGIN_HAS_INLINE_DISPLAY) 1765 1766 patchcanvas.setGroupAsPlugin(clientId, pluginId, hasCustomUI, hasInlineDisplay) 1767 1768 @pyqtSlot(int) 1769 def slot_handlePatchbayClientRemovedCallback(self, clientId): 1770 patchcanvas.removeGroup(clientId) 1771 self.updateMiniCanvasLater() 1772 1773 @pyqtSlot(int, str) 1774 def slot_handlePatchbayClientRenamedCallback(self, clientId, newClientName): 1775 patchcanvas.renameGroup(clientId, newClientName) 1776 self.updateMiniCanvasLater() 1777 1778 @pyqtSlot(int, int, int) 1779 def slot_handlePatchbayClientDataChangedCallback(self, clientId, clientIcon, pluginId): 1780 pcIcon = patchcanvas.ICON_APPLICATION 1781 1782 if clientIcon == PATCHBAY_ICON_PLUGIN: 1783 pcIcon = patchcanvas.ICON_PLUGIN 1784 if clientIcon == PATCHBAY_ICON_HARDWARE: 1785 pcIcon = patchcanvas.ICON_HARDWARE 1786 elif clientIcon == PATCHBAY_ICON_CARLA: 1787 pass 1788 elif clientIcon == PATCHBAY_ICON_DISTRHO: 1789 pcIcon = patchcanvas.ICON_DISTRHO 1790 elif clientIcon == PATCHBAY_ICON_FILE: 1791 pcIcon = patchcanvas.ICON_FILE 1792 1793 patchcanvas.setGroupIcon(clientId, pcIcon) 1794 self.updateMiniCanvasLater() 1795 1796 if pluginId < 0: 1797 return 1798 if pluginId >= self.fPluginCount and pluginId != MAIN_CARLA_PLUGIN_ID: 1799 print("sorry, can't map this plugin to canvas client", pluginId, self.fPluginCount) 1800 return 1801 1802 if pluginId == MAIN_CARLA_PLUGIN_ID: 1803 hasCustomUI = False 1804 hasInlineDisplay = False 1805 else: 1806 hints = self.host.get_plugin_info(pluginId)['hints'] 1807 hasCustomUI = bool(hints & PLUGIN_HAS_CUSTOM_UI) 1808 hasInlineDisplay = bool(hints & PLUGIN_HAS_INLINE_DISPLAY) 1809 1810 patchcanvas.setGroupAsPlugin(clientId, pluginId, hasCustomUI, hasInlineDisplay) 1811 1812 @pyqtSlot(int, int, int, int, int) 1813 def slot_handlePatchbayClientPositionChangedCallback(self, clientId, x1, y1, x2, y2): 1814 if (x1 != 0 and x2 != 0) or (y1 != 0 and y2 != 0): 1815 patchcanvas.splitGroup(clientId) 1816 else: 1817 patchcanvas.joinGroup(clientId) 1818 patchcanvas.setGroupPosFull(clientId, x1, y1, x2, y2) 1819 self.updateMiniCanvasLater() 1820 1821 @pyqtSlot(int, int, int, int, str) 1822 def slot_handlePatchbayPortAddedCallback(self, clientId, portId, portFlags, portGroupId, portName): 1823 if portFlags & PATCHBAY_PORT_IS_INPUT: 1824 portMode = patchcanvas.PORT_MODE_INPUT 1825 else: 1826 portMode = patchcanvas.PORT_MODE_OUTPUT 1827 1828 if portFlags & PATCHBAY_PORT_TYPE_AUDIO: 1829 portType = patchcanvas.PORT_TYPE_AUDIO_JACK 1830 isAlternate = False 1831 elif portFlags & PATCHBAY_PORT_TYPE_CV: 1832 portType = patchcanvas.PORT_TYPE_PARAMETER 1833 isAlternate = False 1834 elif portFlags & PATCHBAY_PORT_TYPE_MIDI: 1835 portType = patchcanvas.PORT_TYPE_MIDI_JACK 1836 isAlternate = False 1837 else: 1838 portType = patchcanvas.PORT_TYPE_NULL 1839 isAlternate = False 1840 1841 patchcanvas.addPort(clientId, portId, portName, portMode, portType, isAlternate) 1842 self.updateMiniCanvasLater() 1843 1844 @pyqtSlot(int, int) 1845 def slot_handlePatchbayPortRemovedCallback(self, groupId, portId): 1846 patchcanvas.removePort(groupId, portId) 1847 self.updateMiniCanvasLater() 1848 1849 @pyqtSlot(int, int, int, int, str) 1850 def slot_handlePatchbayPortChangedCallback(self, groupId, portId, portFlags, portGroupId, newPortName): 1851 patchcanvas.renamePort(groupId, portId, newPortName) 1852 self.updateMiniCanvasLater() 1853 1854 @pyqtSlot(int, int, int, str) 1855 def slot_handlePatchbayPortGroupAddedCallback(self, groupId, portId, portGroupId, newPortName): 1856 # TODO 1857 pass 1858 1859 @pyqtSlot(int, int) 1860 def slot_handlePatchbayPortGroupRemovedCallback(self, groupId, portId): 1861 # TODO 1862 pass 1863 1864 @pyqtSlot(int, int, int, str) 1865 def slot_handlePatchbayPortGroupChangedCallback(self, groupId, portId, portGroupId, newPortName): 1866 # TODO 1867 pass 1868 1869 @pyqtSlot(int, int, int, int, int) 1870 def slot_handlePatchbayConnectionAddedCallback(self, connectionId, groupOutId, portOutId, groupInId, portInId): 1871 patchcanvas.connectPorts(connectionId, groupOutId, portOutId, groupInId, portInId) 1872 self.updateMiniCanvasLater() 1873 1874 @pyqtSlot(int, int, int) 1875 def slot_handlePatchbayConnectionRemovedCallback(self, connectionId, portOutId, portInId): 1876 patchcanvas.disconnectPorts(connectionId) 1877 self.updateMiniCanvasLater() 1878 1879 # -------------------------------------------------------------------------------------------------------- 1880 # Settings 1881 1882 def saveSettings(self): 1883 settings = QSafeSettings() 1884 1885 settings.setValue("Geometry", self.saveGeometry()) 1886 settings.setValue("ShowToolbar", self.ui.toolBar.isEnabled()) 1887 settings.setValue("ShowSidePanel", self.ui.dockWidget.isEnabled()) 1888 1889 diskFolders = [] 1890 1891 for i in range(self.ui.cb_disk.count()): 1892 diskFolders.append(self.ui.cb_disk.itemData(i)) 1893 1894 settings.setValue("DiskFolders", diskFolders) 1895 settings.setValue("LastBPM", self.fLastTransportBPM) 1896 1897 settings.setValue("ShowMeters", self.ui.act_settings_show_meters.isChecked()) 1898 settings.setValue("ShowKeyboard", self.ui.act_settings_show_keyboard.isChecked()) 1899 settings.setValue("HorizontalScrollBarValue", self.ui.graphicsView.horizontalScrollBar().value()) 1900 settings.setValue("VerticalScrollBarValue", self.ui.graphicsView.verticalScrollBar().value()) 1901 1902 settings.setValue(CARLA_KEY_ENGINE_TRANSPORT_MODE, self.host.transportMode) 1903 settings.setValue(CARLA_KEY_ENGINE_TRANSPORT_EXTRA, self.host.transportExtra) 1904 1905 return settings 1906 1907 def loadSettings(self, firstTime): 1908 settings = QSafeSettings() 1909 1910 if firstTime: 1911 geometry = settings.value("Geometry", QByteArray(), QByteArray) 1912 if not geometry.isNull(): 1913 self.restoreGeometry(geometry) 1914 1915 showToolbar = settings.value("ShowToolbar", True, bool) 1916 self.ui.act_settings_show_toolbar.setChecked(showToolbar) 1917 self.ui.toolBar.setEnabled(showToolbar) 1918 self.ui.toolBar.setVisible(showToolbar) 1919 1920 #if settings.contains("SplitterState"): 1921 #self.ui.splitter.restoreState(settings.value("SplitterState", b"")) 1922 #else: 1923 #self.ui.splitter.setSizes([210, 99999]) 1924 1925 showSidePanel = settings.value("ShowSidePanel", True, bool) 1926 self.ui.act_settings_show_side_panel.setChecked(showSidePanel) 1927 self.slot_showSidePanel(showSidePanel) 1928 1929 diskFolders = settings.value("DiskFolders", [HOME], list) 1930 1931 self.ui.cb_disk.setItemData(0, HOME) 1932 1933 for i in range(len(diskFolders)): 1934 if i == 0: continue 1935 folder = diskFolders[i] 1936 self.ui.cb_disk.addItem(os.path.basename(folder), folder) 1937 1938 #if MACOS and not settings.value(CARLA_KEY_MAIN_USE_PRO_THEME, True, bool): 1939 # self.setUnifiedTitleAndToolBarOnMac(True) 1940 1941 showMeters = settings.value("ShowMeters", True, bool) 1942 self.ui.act_settings_show_meters.setChecked(showMeters) 1943 self.ui.peak_in.setVisible(showMeters) 1944 self.ui.peak_out.setVisible(showMeters) 1945 1946 showKeyboard = settings.value("ShowKeyboard", True, bool) 1947 self.ui.act_settings_show_keyboard.setChecked(showKeyboard) 1948 self.ui.scrollArea.setVisible(showKeyboard) 1949 1950 settingsDBf = QSafeSettings("falkTX", "CarlaDatabase2") 1951 self.fFavoritePlugins = settingsDBf.value("PluginDatabase/Favorites", [], list) 1952 1953 QTimer.singleShot(100, self.slot_restoreCanvasScrollbarValues) 1954 1955 # TODO - complete this 1956 oldSettings = self.fSavedSettings 1957 1958 self.fSavedSettings = { 1959 CARLA_KEY_MAIN_PROJECT_FOLDER: settings.value(CARLA_KEY_MAIN_PROJECT_FOLDER, CARLA_DEFAULT_MAIN_PROJECT_FOLDER, str), 1960 CARLA_KEY_MAIN_CONFIRM_EXIT: settings.value(CARLA_KEY_MAIN_CONFIRM_EXIT, CARLA_DEFAULT_MAIN_CONFIRM_EXIT, bool), 1961 CARLA_KEY_MAIN_CLASSIC_SKIN: settings.value(CARLA_KEY_MAIN_CLASSIC_SKIN, CARLA_DEFAULT_MAIN_CLASSIC_SKIN, bool), 1962 CARLA_KEY_MAIN_REFRESH_INTERVAL: settings.value(CARLA_KEY_MAIN_REFRESH_INTERVAL, CARLA_DEFAULT_MAIN_REFRESH_INTERVAL, int), 1963 CARLA_KEY_MAIN_SYSTEM_ICONS: settings.value(CARLA_KEY_MAIN_SYSTEM_ICONS, CARLA_DEFAULT_MAIN_SYSTEM_ICONS, bool), 1964 CARLA_KEY_MAIN_EXPERIMENTAL: settings.value(CARLA_KEY_MAIN_EXPERIMENTAL, CARLA_DEFAULT_MAIN_EXPERIMENTAL, bool), 1965 CARLA_KEY_CANVAS_THEME: settings.value(CARLA_KEY_CANVAS_THEME, CARLA_DEFAULT_CANVAS_THEME, str), 1966 CARLA_KEY_CANVAS_SIZE: settings.value(CARLA_KEY_CANVAS_SIZE, CARLA_DEFAULT_CANVAS_SIZE, str), 1967 CARLA_KEY_CANVAS_AUTO_HIDE_GROUPS: settings.value(CARLA_KEY_CANVAS_AUTO_HIDE_GROUPS, CARLA_DEFAULT_CANVAS_AUTO_HIDE_GROUPS, bool), 1968 CARLA_KEY_CANVAS_AUTO_SELECT_ITEMS: settings.value(CARLA_KEY_CANVAS_AUTO_SELECT_ITEMS, CARLA_DEFAULT_CANVAS_AUTO_SELECT_ITEMS, bool), 1969 CARLA_KEY_CANVAS_USE_BEZIER_LINES: settings.value(CARLA_KEY_CANVAS_USE_BEZIER_LINES, CARLA_DEFAULT_CANVAS_USE_BEZIER_LINES, bool), 1970 CARLA_KEY_CANVAS_EYE_CANDY: settings.value(CARLA_KEY_CANVAS_EYE_CANDY, CARLA_DEFAULT_CANVAS_EYE_CANDY, bool), 1971 CARLA_KEY_CANVAS_FANCY_EYE_CANDY: settings.value(CARLA_KEY_CANVAS_FANCY_EYE_CANDY, CARLA_DEFAULT_CANVAS_FANCY_EYE_CANDY, bool), 1972 CARLA_KEY_CANVAS_USE_OPENGL: settings.value(CARLA_KEY_CANVAS_USE_OPENGL, CARLA_DEFAULT_CANVAS_USE_OPENGL, bool), 1973 CARLA_KEY_CANVAS_ANTIALIASING: settings.value(CARLA_KEY_CANVAS_ANTIALIASING, CARLA_DEFAULT_CANVAS_ANTIALIASING, int), 1974 CARLA_KEY_CANVAS_HQ_ANTIALIASING: settings.value(CARLA_KEY_CANVAS_HQ_ANTIALIASING, CARLA_DEFAULT_CANVAS_HQ_ANTIALIASING, bool), 1975 CARLA_KEY_CANVAS_FULL_REPAINTS: settings.value(CARLA_KEY_CANVAS_FULL_REPAINTS, CARLA_DEFAULT_CANVAS_FULL_REPAINTS, bool), 1976 CARLA_KEY_CUSTOM_PAINTING: (settings.value(CARLA_KEY_MAIN_USE_PRO_THEME, True, bool) and 1977 settings.value(CARLA_KEY_MAIN_PRO_THEME_COLOR, "Black", str).lower() == "black"), 1978 } 1979 1980 if not self.host.isControl: 1981 self.fSavedSettings[CARLA_KEY_CANVAS_INLINE_DISPLAYS] = settings.value(CARLA_KEY_CANVAS_INLINE_DISPLAYS, CARLA_DEFAULT_CANVAS_INLINE_DISPLAYS, bool) 1982 else: 1983 self.fSavedSettings[CARLA_KEY_CANVAS_INLINE_DISPLAYS] = False 1984 1985 settings2 = QSafeSettings("falkTX", "Carla2") 1986 1987 if self.host.experimental: 1988 visible = settings2.value(CARLA_KEY_EXPERIMENTAL_JACK_APPS, CARLA_DEFAULT_EXPERIMENTAL_JACK_APPS, bool) 1989 self.ui.act_plugin_add_jack.setVisible(visible) 1990 else: 1991 self.ui.act_plugin_add_jack.setVisible(False) 1992 1993 self.fMiniCanvasUpdateTimeout = 1000 if self.fSavedSettings[CARLA_KEY_CANVAS_FANCY_EYE_CANDY] else 0 1994 1995 setEngineSettings(self.host) 1996 self.restartTimersIfNeeded() 1997 1998 if oldSettings.get(CARLA_KEY_MAIN_CLASSIC_SKIN, None) not in (self.fSavedSettings[CARLA_KEY_MAIN_CLASSIC_SKIN], None): 1999 newSkin = "classic" if self.fSavedSettings[CARLA_KEY_MAIN_CLASSIC_SKIN] else None 2000 2001 for pitem in self.fPluginList: 2002 if pitem is None: 2003 continue 2004 pitem.recreateWidget(newSkin = newSkin) 2005 2006 return settings 2007 2008 # -------------------------------------------------------------------------------------------------------- 2009 # Settings (helpers) 2010 2011 def enableTransport(self, enabled): 2012 self.ui.group_transport_controls.setEnabled(enabled) 2013 self.ui.group_transport_settings.setEnabled(enabled) 2014 2015 @pyqtSlot() 2016 def slot_restoreCanvasScrollbarValues(self): 2017 settings = QSafeSettings() 2018 horiz = settings.value("HorizontalScrollBarValue", int(self.ui.graphicsView.horizontalScrollBar().maximum()/2), int) 2019 vertc = settings.value("VerticalScrollBarValue", int(self.ui.graphicsView.verticalScrollBar().maximum()/2), int) 2020 self.ui.graphicsView.horizontalScrollBar().setValue(horiz) 2021 self.ui.graphicsView.verticalScrollBar().setValue(vertc) 2022 2023 # -------------------------------------------------------------------------------------------------------- 2024 # Settings (menu actions) 2025 2026 @pyqtSlot(bool) 2027 def slot_showSidePanel(self, yesNo): 2028 self.ui.dockWidget.setEnabled(yesNo) 2029 self.ui.dockWidget.setVisible(yesNo) 2030 2031 @pyqtSlot(bool) 2032 def slot_showToolbar(self, yesNo): 2033 self.ui.toolBar.setEnabled(yesNo) 2034 self.ui.toolBar.setVisible(yesNo) 2035 2036 @pyqtSlot(bool) 2037 def slot_showCanvasMeters(self, yesNo): 2038 self.ui.peak_in.setVisible(yesNo) 2039 self.ui.peak_out.setVisible(yesNo) 2040 QTimer.singleShot(0, self.slot_miniCanvasCheckAll) 2041 2042 @pyqtSlot(bool) 2043 def slot_showCanvasKeyboard(self, yesNo): 2044 self.ui.scrollArea.setVisible(yesNo) 2045 QTimer.singleShot(0, self.slot_miniCanvasCheckAll) 2046 2047 @pyqtSlot() 2048 def slot_configureCarla(self): 2049 dialog = CarlaSettingsW(self.fParentOrSelf, self.host, True, hasGL) 2050 if not dialog.exec_(): 2051 return 2052 2053 self.loadSettings(False) 2054 2055 if self.fWithCanvas: 2056 patchcanvas.clear() 2057 self.setupCanvas() 2058 self.slot_miniCanvasCheckAll() 2059 2060 if self.host.processMode == ENGINE_PROCESS_MODE_CONTINUOUS_RACK and self.host.isPlugin: 2061 pass 2062 elif self.host.is_engine_running(): 2063 self.host.patchbay_refresh(self.fExternalPatchbay) 2064 2065 # -------------------------------------------------------------------------------------------------------- 2066 # About (menu actions) 2067 2068 @pyqtSlot() 2069 def slot_aboutCarla(self): 2070 CarlaAboutW(self.fParentOrSelf, self.host).exec_() 2071 2072 @pyqtSlot() 2073 def slot_aboutJuce(self): 2074 JuceAboutW(self.fParentOrSelf).exec_() 2075 2076 @pyqtSlot() 2077 def slot_aboutQt(self): 2078 QApplication.instance().aboutQt() 2079 2080 # -------------------------------------------------------------------------------------------------------- 2081 # Disk (menu actions) 2082 2083 @pyqtSlot(int) 2084 def slot_diskFolderChanged(self, index): 2085 if index < 0: 2086 return 2087 elif index == 0: 2088 filename = HOME 2089 self.ui.b_disk_remove.setEnabled(False) 2090 else: 2091 filename = self.ui.cb_disk.itemData(index) 2092 self.ui.b_disk_remove.setEnabled(True) 2093 2094 self.fDirModel.setRootPath(filename) 2095 self.ui.fileTreeView.setRootIndex(self.fDirModel.index(filename)) 2096 2097 @pyqtSlot() 2098 def slot_diskFolderAdd(self): 2099 newPath = QFileDialog.getExistingDirectory(self, self.tr("New Folder"), "", QFileDialog.ShowDirsOnly) 2100 2101 if newPath: 2102 if newPath[-1] == os.sep: 2103 newPath = newPath[:-1] 2104 self.ui.cb_disk.addItem(os.path.basename(newPath), newPath) 2105 self.ui.cb_disk.setCurrentIndex(self.ui.cb_disk.count()-1) 2106 self.ui.b_disk_remove.setEnabled(True) 2107 2108 @pyqtSlot() 2109 def slot_diskFolderRemove(self): 2110 index = self.ui.cb_disk.currentIndex() 2111 2112 if index <= 0: 2113 return 2114 2115 self.ui.cb_disk.removeItem(index) 2116 2117 if self.ui.cb_disk.currentIndex() == 0: 2118 self.ui.b_disk_remove.setEnabled(False) 2119 2120 @pyqtSlot(QModelIndex) 2121 def slot_fileTreeDoubleClicked(self, modelIndex): 2122 filename = self.fDirModel.filePath(modelIndex) 2123 2124 if not self.ui.listWidget.isDragUrlValid(filename): 2125 return 2126 2127 if not self.host.load_file(filename): 2128 CustomMessageBox(self, QMessageBox.Critical, self.tr("Error"), 2129 self.tr("Failed to load file"), 2130 self.host.get_last_error(), QMessageBox.Ok, QMessageBox.Ok) 2131 return 2132 2133 if filename.endswith(".carxp"): 2134 self.loadExternalCanvasGroupPositionsIfNeeded(filename) 2135 2136 # -------------------------------------------------------------------------------------------------------- 2137 # Transport 2138 2139 def refreshTransport(self, forced = False): 2140 if not self.ui.l_transport_time.isVisible(): 2141 return 2142 if self.fSampleRate == 0.0 or not self.host.is_engine_running(): 2143 return 2144 2145 timeInfo = self.host.get_transport_info() 2146 playing = timeInfo['playing'] 2147 frame = timeInfo['frame'] 2148 bpm = timeInfo['bpm'] 2149 2150 if playing != self.fLastTransportState or forced: 2151 if playing: 2152 if self.fSavedSettings[CARLA_KEY_MAIN_SYSTEM_ICONS]: 2153 icon = getIcon('media-playback-pause', 16, 'svgz') 2154 else: 2155 icon = QIcon(":/16x16/media-playback-pause.svgz") 2156 self.ui.b_transport_play.setChecked(True) 2157 self.ui.b_transport_play.setIcon(icon) 2158 #self.ui.b_transport_play.setText(self.tr("&Pause")) 2159 else: 2160 if self.fSavedSettings[CARLA_KEY_MAIN_SYSTEM_ICONS]: 2161 icon = getIcon('media-playback-start', 16, 'svgz') 2162 else: 2163 icon = QIcon(":/16x16/media-playback-start.svgz") 2164 self.ui.b_transport_play.setChecked(False) 2165 self.ui.b_transport_play.setIcon(icon) 2166 #self.ui.b_play.setText(self.tr("&Play")) 2167 2168 self.fLastTransportState = playing 2169 2170 if frame != self.fLastTransportFrame or forced: 2171 self.fLastTransportFrame = frame 2172 2173 time = frame / self.fSampleRate 2174 secs = time % 60 2175 mins = (time / 60) % 60 2176 hrs = (time / 3600) % 60 2177 self.ui.l_transport_time.setText("%02i:%02i:%02i" % (hrs, mins, secs)) 2178 2179 frame1 = frame % 1000 2180 frame2 = (frame / 1000) % 1000 2181 frame3 = (frame / 1000000) % 1000 2182 self.ui.l_transport_frame.setText("%03i'%03i'%03i" % (frame3, frame2, frame1)) 2183 2184 bar = timeInfo['bar'] 2185 beat = timeInfo['beat'] 2186 tick = timeInfo['tick'] 2187 self.ui.l_transport_bbt.setText("%03i|%02i|%04i" % (bar, beat, tick)) 2188 2189 if bpm != self.fLastTransportBPM or forced: 2190 self.fLastTransportBPM = bpm 2191 2192 if bpm > 0.0: 2193 self.ui.dsb_transport_bpm.blockSignals(True) 2194 self.ui.dsb_transport_bpm.setValue(bpm) 2195 self.ui.dsb_transport_bpm.blockSignals(False) 2196 self.ui.dsb_transport_bpm.setStyleSheet("") 2197 else: 2198 self.ui.dsb_transport_bpm.setStyleSheet("QDoubleSpinBox { color: palette(mid); }") 2199 2200 # -------------------------------------------------------------------------------------------------------- 2201 # Transport (menu actions) 2202 2203 @pyqtSlot(bool) 2204 def slot_transportPlayPause(self, toggled): 2205 if self.host.isPlugin or not self.host.is_engine_running(): 2206 return 2207 2208 if toggled: 2209 self.host.transport_play() 2210 else: 2211 self.host.transport_pause() 2212 2213 self.refreshTransport() 2214 2215 @pyqtSlot() 2216 def slot_transportStop(self): 2217 if self.host.isPlugin or not self.host.is_engine_running(): 2218 return 2219 2220 self.host.transport_pause() 2221 self.host.transport_relocate(0) 2222 2223 self.refreshTransport() 2224 2225 @pyqtSlot() 2226 def slot_transportBackwards(self): 2227 if self.host.isPlugin or not self.host.is_engine_running(): 2228 return 2229 2230 newFrame = self.host.get_current_transport_frame() - 100000 2231 2232 if newFrame < 0: 2233 newFrame = 0 2234 2235 self.host.transport_relocate(newFrame) 2236 2237 @pyqtSlot(float) 2238 def slot_transportBpmChanged(self, newValue): 2239 self.host.transport_bpm(newValue) 2240 2241 @pyqtSlot() 2242 def slot_transportForwards(self): 2243 if self.fSampleRate == 0.0 or self.host.isPlugin or not self.host.is_engine_running(): 2244 return 2245 2246 newFrame = self.host.get_current_transport_frame() + int(self.fSampleRate*2.5) 2247 self.host.transport_relocate(newFrame) 2248 2249 @pyqtSlot(bool) 2250 def slot_transportJackEnabled(self, clicked): 2251 if not self.host.is_engine_running(): 2252 return 2253 self.host.transportMode = ENGINE_TRANSPORT_MODE_JACK if clicked else ENGINE_TRANSPORT_MODE_INTERNAL 2254 self.host.set_engine_option(ENGINE_OPTION_TRANSPORT_MODE, 2255 self.host.transportMode, 2256 self.host.transportExtra) 2257 2258 @pyqtSlot(bool) 2259 def slot_transportLinkEnabled(self, clicked): 2260 if not self.host.is_engine_running(): 2261 return 2262 extra = ":link:" if clicked else "" 2263 self.host.transportExtra = extra 2264 self.host.set_engine_option(ENGINE_OPTION_TRANSPORT_MODE, 2265 self.host.transportMode, 2266 self.host.transportExtra) 2267 2268 # -------------------------------------------------------------------------------------------------------- 2269 # Other 2270 2271 @pyqtSlot(bool) 2272 def slot_xrunClear(self): 2273 self.host.clear_engine_xruns() 2274 2275 # -------------------------------------------------------------------------------------------------------- 2276 # Canvas scrollbars 2277 2278 @pyqtSlot(int) 2279 def slot_horizontalScrollBarChanged(self, value): 2280 maximum = self.ui.graphicsView.horizontalScrollBar().maximum() 2281 if maximum == 0: 2282 xp = 0 2283 else: 2284 xp = float(value) / maximum 2285 self.ui.miniCanvasPreview.setViewPosX(xp) 2286 self.updateCanvasInitialPos() 2287 2288 @pyqtSlot(int) 2289 def slot_verticalScrollBarChanged(self, value): 2290 maximum = self.ui.graphicsView.verticalScrollBar().maximum() 2291 if maximum == 0: 2292 yp = 0 2293 else: 2294 yp = float(value) / maximum 2295 self.ui.miniCanvasPreview.setViewPosY(yp) 2296 self.updateCanvasInitialPos() 2297 2298 # -------------------------------------------------------------------------------------------------------- 2299 # Canvas keyboard 2300 2301 @pyqtSlot(int) 2302 def slot_noteOn(self, note): 2303 if self.fPluginCount == 0: 2304 return 2305 2306 for pluginId in self.fSelectedPlugins: 2307 self.host.send_midi_note(pluginId, 0, note, 100) 2308 2309 pedit = self.getPluginEditDialog(pluginId) 2310 pedit.noteOn(0, note, 100) 2311 2312 @pyqtSlot(int) 2313 def slot_noteOff(self, note): 2314 if self.fPluginCount == 0: 2315 return 2316 2317 for pluginId in self.fSelectedPlugins: 2318 self.host.send_midi_note(pluginId, 0, note, 0) 2319 2320 pedit = self.getPluginEditDialog(pluginId) 2321 pedit.noteOff(0, note) 2322 2323 # -------------------------------------------------------------------------------------------------------- 2324 # Canvas keyboard (host callbacks) 2325 2326 @pyqtSlot(int, int, int, int) 2327 def slot_handleNoteOnCallback(self, pluginId, channel, note, velocity): 2328 if pluginId in self.fSelectedPlugins: 2329 self.ui.keyboard.sendNoteOn(note, False) 2330 2331 @pyqtSlot(int, int, int) 2332 def slot_handleNoteOffCallback(self, pluginId, channel, note): 2333 if pluginId in self.fSelectedPlugins: 2334 self.ui.keyboard.sendNoteOff(note, False) 2335 2336 # -------------------------------------------------------------------------------------------------------- 2337 2338 @pyqtSlot(int) 2339 def slot_handleUpdateCallback(self, pluginId): 2340 pitem = self.getPluginItem(pluginId) 2341 2342 if pitem is None: 2343 return 2344 2345 wasCompacted = pitem.isCompacted() 2346 isCompacted = wasCompacted 2347 2348 check = self.host.get_custom_data_value(pluginId, CUSTOM_DATA_TYPE_PROPERTY, "CarlaSkinIsCompacted") 2349 if not check: 2350 return 2351 isCompacted = bool(check == "true") 2352 2353 if wasCompacted == isCompacted: 2354 return 2355 2356 pitem.recreateWidget(True) 2357 2358 # -------------------------------------------------------------------------------------------------------- 2359 # MiniCanvas stuff 2360 2361 @pyqtSlot() 2362 def slot_miniCanvasCheckAll(self): 2363 self.slot_miniCanvasCheckSize() 2364 self.slot_horizontalScrollBarChanged(self.ui.graphicsView.horizontalScrollBar().value()) 2365 self.slot_verticalScrollBarChanged(self.ui.graphicsView.verticalScrollBar().value()) 2366 2367 @pyqtSlot() 2368 def slot_miniCanvasCheckSize(self): 2369 if self.fCanvasWidth == 0 or self.fCanvasHeight == 0: 2370 return 2371 2372 currentIndex = self.ui.tabWidget.currentIndex() 2373 2374 if currentIndex == 1: 2375 width = self.ui.graphicsView.width() 2376 height = self.ui.graphicsView.height() 2377 else: 2378 self.ui.tabWidget.blockSignals(True) 2379 self.ui.tabWidget.setCurrentIndex(1) 2380 width = self.ui.graphicsView.width() 2381 height = self.ui.graphicsView.height() 2382 self.ui.tabWidget.setCurrentIndex(currentIndex) 2383 self.ui.tabWidget.blockSignals(False) 2384 2385 self.scene.updateLimits() 2386 2387 self.ui.miniCanvasPreview.setViewSize(float(width)/self.fCanvasWidth, float(height)/self.fCanvasHeight) 2388 2389 @pyqtSlot(float, float) 2390 def slot_miniCanvasMoved(self, xp, yp): 2391 hsb = self.ui.graphicsView.horizontalScrollBar() 2392 vsb = self.ui.graphicsView.verticalScrollBar() 2393 hsb.setValue(xp * hsb.maximum()) 2394 vsb.setValue(yp * vsb.maximum()) 2395 self.updateCanvasInitialPos() 2396 2397 # -------------------------------------------------------------------------------------------------------- 2398 # Logs autoscroll, save and clear 2399 2400 @pyqtSlot(int) 2401 def slot_toggleLogAutoscroll(self, checkState): 2402 self.autoscrollOnNewLog = checkState == Qt.Checked 2403 if self.autoscrollOnNewLog: 2404 self.ui.text_logs.verticalScrollBar().setValue(self.ui.text_logs.verticalScrollBar().maximum()) 2405 2406 @pyqtSlot(int) 2407 def slot_logSliderMoved(self, slider_pos): 2408 if self.ui.text_logs.verticalScrollBar().hasTracking() or self.autoscrollOnNewLog: 2409 self.lastLogSliderPos = slider_pos 2410 else: 2411 self.ui.text_logs.verticalScrollBar().setValue(self.lastLogSliderPos) 2412 2413 @pyqtSlot() 2414 def slot_logButtonsState(self, enabled=True): 2415 self.ui.logs_clear.setEnabled(enabled) 2416 self.ui.logs_save.setEnabled(enabled) 2417 2418 @pyqtSlot() 2419 def slot_logSave(self): 2420 filename = os.path.join(self.fSavedSettings[CARLA_KEY_MAIN_PROJECT_FOLDER], 'carla_log.txt') 2421 filename, _ = QFileDialog.getSaveFileName(self, self.tr("Save Logs"), filename) 2422 2423 if not filename: 2424 return 2425 2426 try: 2427 with open(filename, "w") as logfile: 2428 logfile.write(self.ui.text_logs.toPlainText()) 2429 logfile.close() 2430 except: 2431 return 2432 2433 @pyqtSlot() 2434 def slot_logClear(self): 2435 self.ui.text_logs.clear() 2436 self.ui.text_logs.appendPlainText("======= Logs cleared ========") 2437 self.slot_logButtonsState(False) 2438 2439 # -------------------------------------------------------------------------------------------------------- 2440 # Timers 2441 2442 def startTimers(self): 2443 if self.fIdleTimerFast == 0: 2444 self.fIdleTimerFast = self.startTimer(self.fSavedSettings[CARLA_KEY_MAIN_REFRESH_INTERVAL]) 2445 2446 if self.fIdleTimerSlow == 0: 2447 self.fIdleTimerSlow = self.startTimer(self.fSavedSettings[CARLA_KEY_MAIN_REFRESH_INTERVAL]*4) 2448 2449 def restartTimersIfNeeded(self): 2450 if self.fIdleTimerFast != 0: 2451 self.killTimer(self.fIdleTimerFast) 2452 self.fIdleTimerFast = self.startTimer(self.fSavedSettings[CARLA_KEY_MAIN_REFRESH_INTERVAL]) 2453 2454 if self.fIdleTimerSlow != 0: 2455 self.killTimer(self.fIdleTimerSlow) 2456 self.fIdleTimerSlow = self.startTimer(self.fSavedSettings[CARLA_KEY_MAIN_REFRESH_INTERVAL]*4) 2457 2458 def killTimers(self): 2459 if self.fIdleTimerFast != 0: 2460 self.killTimer(self.fIdleTimerFast) 2461 self.fIdleTimerFast = 0 2462 2463 if self.fIdleTimerSlow != 0: 2464 self.killTimer(self.fIdleTimerSlow) 2465 self.fIdleTimerSlow = 0 2466 2467 # -------------------------------------------------------------------------------------------------------- 2468 # Misc 2469 2470 @pyqtSlot(int) 2471 def slot_tabChanged(self, index): 2472 if index != 1: 2473 return 2474 2475 self.ui.graphicsView.setFocus() 2476 2477 @pyqtSlot(int) 2478 def slot_handleReloadAllCallback(self, pluginId): 2479 if pluginId >= self.fPluginCount: 2480 return 2481 2482 pitem = self.fPluginList[pluginId] 2483 if pitem is None: 2484 return 2485 2486 pitem.recreateWidget() 2487 2488 # -------------------------------------------------------------------------------------------------------- 2489 2490 @pyqtSlot(int, int, str) 2491 def slot_handleNSMCallback(self, opcode, valueInt, valueStr): 2492 if opcode == NSM_CALLBACK_INIT: 2493 return 2494 2495 # Error 2496 elif opcode == NSM_CALLBACK_ERROR: 2497 pass 2498 2499 # Reply 2500 elif opcode == NSM_CALLBACK_ANNOUNCE: 2501 self.fFirstEngineInit = False 2502 self.fSessionManagerName = valueStr 2503 self.setProperWindowTitle() 2504 2505 # If NSM server does not support optional-gui, revert our initial assumptions that it does 2506 if (valueInt & (1 << 1)) == 0x0: 2507 self.fWindowCloseHideGui = False 2508 self.ui.act_file_quit.setText(self.tr("&Quit")) 2509 QApplication.instance().setQuitOnLastWindowClosed(True) 2510 self.show() 2511 2512 # Open 2513 elif opcode == NSM_CALLBACK_OPEN: 2514 self.fProjectFilename = QFileInfo(valueStr+".carxp").absoluteFilePath() 2515 self.setProperWindowTitle() 2516 2517 self.fCustomStopAction = self.CUSTOM_ACTION_PROJECT_LOAD 2518 self.slot_engineStop(True) 2519 return 2520 2521 # Save 2522 elif opcode == NSM_CALLBACK_SAVE: 2523 self.saveProjectNow() 2524 2525 # Session is Loaded 2526 elif opcode == NSM_CALLBACK_SESSION_IS_LOADED: 2527 pass 2528 2529 # Show Optional Gui 2530 elif opcode == NSM_CALLBACK_SHOW_OPTIONAL_GUI: 2531 self.show() 2532 2533 # Hide Optional Gui 2534 elif opcode == NSM_CALLBACK_HIDE_OPTIONAL_GUI: 2535 self.hideForNSM() 2536 2537 # Set client name 2538 elif opcode == NSM_CALLBACK_SET_CLIENT_NAME_ID: 2539 self.fClientName = valueStr 2540 return 2541 2542 self.host.nsm_ready(opcode) 2543 2544 # -------------------------------------------------------------------------------------------------------- 2545 2546 def fixLogText(self, text): 2547 return text.replace("\x1b[30;1m", "").replace("\x1b[31m", "").replace("\x1b[0m", "") 2548 2549 @pyqtSlot(int, int, int, int, float, str) 2550 def slot_handleDebugCallback(self, pluginId, value1, value2, value3, valuef, valueStr): 2551 self.ui.text_logs.appendPlainText(self.fixLogText(valueStr)) 2552 2553 @pyqtSlot(str) 2554 def slot_handleInfoCallback(self, info): 2555 QMessageBox.information(self, "Information", info) 2556 2557 @pyqtSlot(str) 2558 def slot_handleErrorCallback(self, error): 2559 QMessageBox.critical(self, "Error", error) 2560 2561 @pyqtSlot() 2562 def slot_handleQuitCallback(self): 2563 self.fIsProjectLoading = False 2564 self.killTimers() 2565 self.removeAllPlugins() 2566 self.projectLoadingFinished(False) 2567 2568 @pyqtSlot(int) 2569 def slot_handleInlineDisplayRedrawCallback(self, pluginId): 2570 # FIXME 2571 if self.fIdleTimerSlow != 0 and self.fIdleTimerFast != 0 and pluginId < self.fPluginCount and not self.fIsProjectLoading: 2572 patchcanvas.redrawPluginGroup(pluginId) 2573 2574 # -------------------------------------------------------------------------------------------------------- 2575 2576 @pyqtSlot() 2577 def slot_handleSIGUSR1(self): 2578 print("Got SIGUSR1 -> Saving project now") 2579 self.slot_fileSave() 2580 2581 @pyqtSlot() 2582 def slot_handleSIGTERM(self): 2583 print("Got SIGTERM -> Closing now") 2584 self.fCustomStopAction = self.CUSTOM_ACTION_APP_CLOSE 2585 self.close() 2586 2587 # -------------------------------------------------------------------------------------------------------- 2588 # Internal stuff 2589 2590 def getExtraPtr(self, plugin): 2591 ptype = plugin['type'] 2592 2593 if ptype == PLUGIN_LADSPA: 2594 uniqueId = plugin['uniqueId'] 2595 2596 self.maybeLoadRDFs() 2597 2598 for rdfItem in self.fLadspaRdfList: 2599 if rdfItem.UniqueID == uniqueId: 2600 return pointer(rdfItem) 2601 2602 elif ptype == PLUGIN_SF2: 2603 if plugin['name'].lower().endswith(" (16 outputs)"): 2604 return self._true 2605 2606 return None 2607 2608 def maybeLoadRDFs(self): 2609 if not self.fLadspaRdfNeedsUpdate: 2610 return 2611 2612 self.fLadspaRdfNeedsUpdate = False 2613 self.fLadspaRdfList = [] 2614 2615 if not haveLRDF: 2616 return 2617 2618 settingsDir = os.path.join(HOME, ".config", "falkTX") 2619 frLadspaFile = os.path.join(settingsDir, "ladspa_rdf.db") 2620 2621 if os.path.exists(frLadspaFile): 2622 frLadspa = open(frLadspaFile, 'r') 2623 2624 try: 2625 self.fLadspaRdfList = ladspa_rdf.get_c_ladspa_rdfs(json.load(frLadspa)) 2626 except: 2627 pass 2628 2629 frLadspa.close() 2630 2631 # -------------------------------------------------------------------------------------------------------- 2632 2633 def getPluginCount(self): 2634 return self.fPluginCount 2635 2636 def getPluginItem(self, pluginId): 2637 if pluginId >= self.fPluginCount: 2638 return None 2639 2640 pitem = self.fPluginList[pluginId] 2641 if pitem is None: 2642 return None 2643 #if False: 2644 #return CarlaRackItem(self, 0, False) 2645 2646 return pitem 2647 2648 def getPluginEditDialog(self, pluginId): 2649 if pluginId >= self.fPluginCount: 2650 return None 2651 2652 pitem = self.fPluginList[pluginId] 2653 if pitem is None: 2654 return None 2655 if False: 2656 return PluginEdit(self, self.host, 0) 2657 2658 return pitem.getEditDialog() 2659 2660 def getPluginSlotWidget(self, pluginId): 2661 if pluginId >= self.fPluginCount: 2662 return None 2663 2664 pitem = self.fPluginList[pluginId] 2665 if pitem is None: 2666 return None 2667 #if False: 2668 #return AbstractPluginSlot() 2669 2670 return pitem.getWidget() 2671 2672 # -------------------------------------------------------------------------------------------------------- 2673 2674 def waitForPendingEvents(self): 2675 pass 2676 2677 # -------------------------------------------------------------------------------------------------------- 2678 # show/hide event 2679 2680 def showEvent(self, event): 2681 self.getAndRefreshRuntimeInfo() 2682 self.refreshTransport(True) 2683 QMainWindow.showEvent(self, event) 2684 2685 if QT_VERSION >= 0x50600: 2686 self.host.set_engine_option(ENGINE_OPTION_FRONTEND_UI_SCALE, int(self.devicePixelRatioF() * 1000), "") 2687 print("Frontend pixel ratio is", self.devicePixelRatioF()) 2688 2689 # set our gui as parent for all plugins UIs 2690 if self.host.manageUIs and not self.host.isControl: 2691 if MACOS: 2692 nsViewPtr = int(self.winId()) 2693 winIdStr = "%x" % gCarla.utils.cocoa_get_window(nsViewPtr) 2694 else: 2695 winIdStr = "%x" % int(self.winId()) 2696 self.host.set_engine_option(ENGINE_OPTION_FRONTEND_WIN_ID, 0, winIdStr) 2697 2698 def hideEvent(self, event): 2699 # disable parent 2700 if not self.host.isControl: 2701 self.host.set_engine_option(ENGINE_OPTION_FRONTEND_WIN_ID, 0, "0") 2702 2703 QMainWindow.hideEvent(self, event) 2704 2705 # -------------------------------------------------------------------------------------------------------- 2706 # resize event 2707 2708 def resizeEvent(self, event): 2709 QMainWindow.resizeEvent(self, event) 2710 2711 if self.fWithCanvas: 2712 self.slot_miniCanvasCheckSize() 2713 2714 # -------------------------------------------------------------------------------------------------------- 2715 # timer event 2716 2717 def refreshRuntimeInfo(self, load, xruns): 2718 txt1 = str(xruns) if (xruns >= 0) else "--" 2719 txt2 = "" if (xruns == 1) else "s" 2720 self.ui.b_xruns.setText("%s Xrun%s" % (txt1, txt2)) 2721 self.ui.pb_dsp_load.setValue(int(load)) 2722 2723 def getAndRefreshRuntimeInfo(self): 2724 if not self.ui.pb_dsp_load.isVisible(): 2725 return 2726 if not self.host.is_engine_running(): 2727 return 2728 info = self.host.get_runtime_engine_info() 2729 self.refreshRuntimeInfo(info['load'], info['xruns']) 2730 2731 def idleFast(self): 2732 self.host.engine_idle() 2733 self.refreshTransport() 2734 2735 if self.fPluginCount == 0 or self.fCurrentlyRemovingAllPlugins: 2736 return 2737 2738 for pitem in self.fPluginList: 2739 if pitem is None: 2740 break 2741 2742 pitem.getWidget().idleFast() 2743 2744 for pluginId in self.fSelectedPlugins: 2745 self.fPeaksCleared = False 2746 if self.ui.peak_in.isVisible(): 2747 self.ui.peak_in.displayMeter(1, self.host.get_input_peak_value(pluginId, True)) 2748 self.ui.peak_in.displayMeter(2, self.host.get_input_peak_value(pluginId, False)) 2749 if self.ui.peak_out.isVisible(): 2750 self.ui.peak_out.displayMeter(1, self.host.get_output_peak_value(pluginId, True)) 2751 self.ui.peak_out.displayMeter(2, self.host.get_output_peak_value(pluginId, False)) 2752 return 2753 2754 if self.fPeaksCleared: 2755 return 2756 2757 self.fPeaksCleared = True 2758 self.ui.peak_in.displayMeter(1, 0.0, True) 2759 self.ui.peak_in.displayMeter(2, 0.0, True) 2760 self.ui.peak_out.displayMeter(1, 0.0, True) 2761 self.ui.peak_out.displayMeter(2, 0.0, True) 2762 2763 def idleSlow(self): 2764 self.getAndRefreshRuntimeInfo() 2765 2766 if self.fPluginCount == 0 or self.fCurrentlyRemovingAllPlugins: 2767 return 2768 2769 for pitem in self.fPluginList: 2770 if pitem is None: 2771 break 2772 2773 pitem.getWidget().idleSlow() 2774 2775 def timerEvent(self, event): 2776 if event.timerId() == self.fIdleTimerFast: 2777 self.idleFast() 2778 2779 elif event.timerId() == self.fIdleTimerSlow: 2780 self.idleSlow() 2781 2782 QMainWindow.timerEvent(self, event) 2783 2784 # -------------------------------------------------------------------------------------------------------- 2785 # color/style change event 2786 2787 def changeEvent(self, event): 2788 if event.type() in (QEvent.PaletteChange, QEvent.StyleChange): 2789 self.updateStyle() 2790 QMainWindow.changeEvent(self, event) 2791 2792 def updateStyle(self): 2793 # Rack padding images setup 2794 rack_imgL = QImage(":/bitmaps/rack_padding_left.png") 2795 rack_imgR = QImage(":/bitmaps/rack_padding_right.png") 2796 2797 min_value = 0.07 2798 2799 if PYQT_VERSION >= 0x50600: 2800 value_fix = 1.0/(1.0-rack_imgL.scaled(1, 1, Qt.IgnoreAspectRatio, Qt.SmoothTransformation).pixelColor(0,0).blackF()) 2801 else: 2802 value_fix = 1.5 2803 2804 rack_pal = self.ui.rack.palette() 2805 bg_color = rack_pal.window().color() 2806 fg_color = rack_pal.text().color() 2807 bg_value = 1.0 - bg_color.blackF() 2808 if bg_value != 0.0 and bg_value < min_value: 2809 pad_color = bg_color.lighter(int(100*min_value/bg_value*value_fix)) 2810 else: 2811 pad_color = QColor.fromHsvF(0.0, 0.0, min_value*value_fix) 2812 2813 painter = QPainter() 2814 fillRect = rack_imgL.rect().adjusted(-1,-1,1,1) 2815 2816 painter.begin(rack_imgL) 2817 painter.setCompositionMode(QPainter.CompositionMode_Multiply) 2818 painter.setBrush(pad_color) 2819 painter.drawRect(fillRect) 2820 painter.end() 2821 rack_pixmapL = QPixmap(rack_imgL) 2822 self.imgL_palette = QPalette() 2823 self.imgL_palette.setBrush(QPalette.Window, QBrush(rack_pixmapL)) 2824 self.ui.pad_left.setPalette(self.imgL_palette) 2825 self.ui.pad_left.setAutoFillBackground(True) 2826 2827 painter.begin(rack_imgR) 2828 painter.setCompositionMode(QPainter.CompositionMode_Multiply) 2829 painter.setBrush(pad_color) 2830 painter.drawRect(fillRect) 2831 painter.end() 2832 rack_pixmapR = QPixmap(rack_imgR) 2833 self.imgR_palette = QPalette() 2834 self.imgR_palette.setBrush(QPalette.Window, QBrush(rack_pixmapR)) 2835 self.ui.pad_right.setPalette(self.imgR_palette) 2836 self.ui.pad_right.setAutoFillBackground(True) 2837 2838 # qt's rgba is actually argb, so convert that 2839 bg_color_value = bg_color.rgba() 2840 bg_color_value = ((bg_color_value & 0xffffff) << 8) | (bg_color_value >> 24) 2841 2842 fg_color_value = fg_color.rgba() 2843 fg_color_value = ((fg_color_value & 0xffffff) << 8) | (fg_color_value >> 24) 2844 2845 self.host.set_engine_option(ENGINE_OPTION_FRONTEND_BACKGROUND_COLOR, bg_color_value, "") 2846 self.host.set_engine_option(ENGINE_OPTION_FRONTEND_FOREGROUND_COLOR, fg_color_value, "") 2847 2848 # -------------------------------------------------------------------------------------------------------- 2849 # paint event 2850 2851 #def paintEvent(self, event): 2852 #QMainWindow.paintEvent(self, event) 2853 2854 #if MACOS or not self.fSavedSettings[CARLA_KEY_CUSTOM_PAINTING]: 2855 #return 2856 2857 #painter = QPainter(self) 2858 #painter.setBrush(QColor(36, 36, 36)) 2859 #painter.setPen(QColor(62, 62, 62)) 2860 #painter.drawRect(1, self.height()/2, self.width()-3, self.height()-self.height()/2-1) 2861 2862 # -------------------------------------------------------------------------------------------------------- 2863 # close event 2864 2865 def shouldIgnoreClose(self): 2866 if self.host.isControl or self.host.isPlugin: 2867 return False 2868 if self.fCustomStopAction == self.CUSTOM_ACTION_APP_CLOSE: 2869 return False 2870 if self.fWindowCloseHideGui: 2871 return False 2872 if self.fSavedSettings[CARLA_KEY_MAIN_CONFIRM_EXIT]: 2873 return QMessageBox.question(self, self.tr("Quit"), 2874 self.tr("Are you sure you want to quit Carla?"), 2875 QMessageBox.Yes|QMessageBox.No) == QMessageBox.No 2876 return False 2877 2878 def closeEvent(self, event): 2879 if self.shouldIgnoreClose(): 2880 event.ignore() 2881 return 2882 2883 if self.fWindowCloseHideGui and self.fCustomStopAction != self.CUSTOM_ACTION_APP_CLOSE: 2884 self.hideForNSM() 2885 self.host.nsm_ready(NSM_CALLBACK_HIDE_OPTIONAL_GUI) 2886 return 2887 2888 patchcanvas.handleAllPluginsRemoved() 2889 2890 if MACOS and self.fMacClosingHelper and not (self.host.isControl or self.host.isPlugin): 2891 self.fCustomStopAction = self.CUSTOM_ACTION_APP_CLOSE 2892 self.fMacClosingHelper = False 2893 event.ignore() 2894 2895 for i in reversed(range(self.fPluginCount)): 2896 self.host.show_custom_ui(i, False) 2897 2898 QTimer.singleShot(100, self.close) 2899 return 2900 2901 self.killTimers() 2902 self.saveSettings() 2903 2904 if self.host.is_engine_running() and not (self.host.isControl or self.host.isPlugin): 2905 if not self.slot_engineStop(True): 2906 self.fCustomStopAction = self.CUSTOM_ACTION_APP_CLOSE 2907 event.ignore() 2908 return 2909 2910 QMainWindow.closeEvent(self, event) 2911 2912 # if we reach this point, fully close ourselves 2913 gCarla.gui = None 2914 QApplication.instance().quit() 2915 2916# ------------------------------------------------------------------------------------------------ 2917# Canvas callback 2918 2919def canvasCallback(action, value1, value2, valueStr): 2920 host = gCarla.gui.host 2921 2922 if gCarla.gui.fCustomStopAction == HostWindow.CUSTOM_ACTION_APP_CLOSE: 2923 return 2924 2925 if action == patchcanvas.ACTION_GROUP_INFO: 2926 pass 2927 2928 elif action == patchcanvas.ACTION_GROUP_RENAME: 2929 pass 2930 2931 elif action == patchcanvas.ACTION_GROUP_SPLIT: 2932 groupId = value1 2933 patchcanvas.splitGroup(groupId) 2934 gCarla.gui.updateMiniCanvasLater() 2935 2936 elif action == patchcanvas.ACTION_GROUP_JOIN: 2937 groupId = value1 2938 patchcanvas.joinGroup(groupId) 2939 gCarla.gui.updateMiniCanvasLater() 2940 2941 elif action == patchcanvas.ACTION_GROUP_POSITION: 2942 if gCarla.gui.fIsProjectLoading: 2943 return 2944 if not host.is_engine_running(): 2945 return 2946 groupId = value1 2947 x1, y1, x2, y2 = tuple(int(i) for i in valueStr.split(":")) 2948 host.patchbay_set_group_pos(gCarla.gui.fExternalPatchbay, groupId, x1, y1, x2, y2) 2949 gCarla.gui.updateMiniCanvasLater() 2950 2951 elif action == patchcanvas.ACTION_PORT_INFO: 2952 pass 2953 2954 elif action == patchcanvas.ACTION_PORT_RENAME: 2955 pass 2956 2957 elif action == patchcanvas.ACTION_PORTS_CONNECT: 2958 gOut, pOut, gIn, pIn = tuple(int(i) for i in valueStr.split(":")) 2959 2960 if not host.patchbay_connect(gCarla.gui.fExternalPatchbay, gOut, pOut, gIn, pIn): 2961 print("Connection failed:", host.get_last_error()) 2962 2963 elif action == patchcanvas.ACTION_PORTS_DISCONNECT: 2964 connectionId = value1 2965 2966 if not host.patchbay_disconnect(gCarla.gui.fExternalPatchbay, connectionId): 2967 print("Disconnect failed:", host.get_last_error()) 2968 2969 elif action == patchcanvas.ACTION_PLUGIN_CLONE: 2970 pluginId = value1 2971 2972 if not host.clone_plugin(pluginId): 2973 CustomMessageBox(gCarla.gui, QMessageBox.Warning, gCarla.gui.tr("Error"), gCarla.gui.tr("Operation failed"), 2974 host.get_last_error(), QMessageBox.Ok, QMessageBox.Ok) 2975 2976 elif action == patchcanvas.ACTION_PLUGIN_EDIT: 2977 pluginId = value1 2978 pwidget = gCarla.gui.getPluginSlotWidget(pluginId) 2979 2980 if pwidget is not None: 2981 pwidget.showEditDialog() 2982 2983 elif action == patchcanvas.ACTION_PLUGIN_RENAME: 2984 pluginId = value1 2985 pwidget = gCarla.gui.getPluginSlotWidget(pluginId) 2986 2987 if pwidget is not None: 2988 pwidget.showRenameDialog() 2989 2990 elif action == patchcanvas.ACTION_PLUGIN_REPLACE: 2991 pluginId = value1 2992 pwidget = gCarla.gui.getPluginSlotWidget(pluginId) 2993 2994 if pwidget is not None: 2995 pwidget.showReplaceDialog() 2996 2997 elif action == patchcanvas.ACTION_PLUGIN_REMOVE: 2998 pluginId = value1 2999 3000 if not host.remove_plugin(pluginId): 3001 CustomMessageBox(gCarla.gui, QMessageBox.Warning, gCarla.gui.tr("Error"), gCarla.gui.tr("Operation failed"), 3002 host.get_last_error(), QMessageBox.Ok, QMessageBox.Ok) 3003 3004 elif action == patchcanvas.ACTION_PLUGIN_SHOW_UI: 3005 pluginId = value1 3006 pwidget = gCarla.gui.getPluginSlotWidget(pluginId) 3007 3008 if pwidget is not None: 3009 pwidget.showCustomUI() 3010 3011 elif action == patchcanvas.ACTION_BG_RIGHT_CLICK: 3012 gCarla.gui.slot_showPluginActionsMenu() 3013 3014 elif action == patchcanvas.ACTION_INLINE_DISPLAY: 3015 if gCarla.gui.fIsProjectLoading: 3016 return 3017 if not host.is_engine_running(): 3018 return 3019 pluginId = value1 3020 width, height = [int(v) for v in valueStr.split(":")] 3021 return host.render_inline_display(pluginId, width, height) 3022 3023# ------------------------------------------------------------------------------------------------------------ 3024# Engine callback 3025 3026def engineCallback(host, action, pluginId, value1, value2, value3, valuef, valueStr): 3027 # kdevelop likes this :) 3028 if False: host = CarlaHostNull() 3029 3030 valueStr = charPtrToString(valueStr) 3031 3032 if action == ENGINE_CALLBACK_ENGINE_STARTED: 3033 host.processMode = value1 3034 host.transportMode = value2 3035 elif action == ENGINE_CALLBACK_PROCESS_MODE_CHANGED: 3036 host.processMode = value1 3037 elif action == ENGINE_CALLBACK_TRANSPORT_MODE_CHANGED: 3038 host.transportMode = value1 3039 host.transportExtra = valueStr 3040 3041 if action == ENGINE_CALLBACK_DEBUG: 3042 host.DebugCallback.emit(pluginId, value1, value2, value3, valuef, valueStr) 3043 elif action == ENGINE_CALLBACK_PLUGIN_ADDED: 3044 host.PluginAddedCallback.emit(pluginId, valueStr) 3045 elif action == ENGINE_CALLBACK_PLUGIN_REMOVED: 3046 host.PluginRemovedCallback.emit(pluginId) 3047 elif action == ENGINE_CALLBACK_PLUGIN_RENAMED: 3048 host.PluginRenamedCallback.emit(pluginId, valueStr) 3049 elif action == ENGINE_CALLBACK_PLUGIN_UNAVAILABLE: 3050 host.PluginUnavailableCallback.emit(pluginId, valueStr) 3051 elif action == ENGINE_CALLBACK_PARAMETER_VALUE_CHANGED: 3052 host.ParameterValueChangedCallback.emit(pluginId, value1, valuef) 3053 elif action == ENGINE_CALLBACK_PARAMETER_DEFAULT_CHANGED: 3054 host.ParameterDefaultChangedCallback.emit(pluginId, value1, valuef) 3055 elif action == ENGINE_CALLBACK_PARAMETER_MAPPED_CONTROL_INDEX_CHANGED: 3056 host.ParameterMappedControlIndexChangedCallback.emit(pluginId, value1, value2) 3057 elif action == ENGINE_CALLBACK_PARAMETER_MAPPED_RANGE_CHANGED: 3058 minimum, maximum = (float(v) for v in valueStr.split(":", 2)) 3059 host.ParameterMappedRangeChangedCallback.emit(pluginId, value1, minimum, maximum) 3060 elif action == ENGINE_CALLBACK_PARAMETER_MIDI_CHANNEL_CHANGED: 3061 host.ParameterMidiChannelChangedCallback.emit(pluginId, value1, value2) 3062 elif action == ENGINE_CALLBACK_PROGRAM_CHANGED: 3063 host.ProgramChangedCallback.emit(pluginId, value1) 3064 elif action == ENGINE_CALLBACK_MIDI_PROGRAM_CHANGED: 3065 host.MidiProgramChangedCallback.emit(pluginId, value1) 3066 elif action == ENGINE_CALLBACK_OPTION_CHANGED: 3067 host.OptionChangedCallback.emit(pluginId, value1, bool(value2)) 3068 elif action == ENGINE_CALLBACK_UI_STATE_CHANGED: 3069 host.UiStateChangedCallback.emit(pluginId, value1) 3070 elif action == ENGINE_CALLBACK_NOTE_ON: 3071 host.NoteOnCallback.emit(pluginId, value1, value2, value3) 3072 elif action == ENGINE_CALLBACK_NOTE_OFF: 3073 host.NoteOffCallback.emit(pluginId, value1, value2) 3074 elif action == ENGINE_CALLBACK_UPDATE: 3075 host.UpdateCallback.emit(pluginId) 3076 elif action == ENGINE_CALLBACK_RELOAD_INFO: 3077 host.ReloadInfoCallback.emit(pluginId) 3078 elif action == ENGINE_CALLBACK_RELOAD_PARAMETERS: 3079 host.ReloadParametersCallback.emit(pluginId) 3080 elif action == ENGINE_CALLBACK_RELOAD_PROGRAMS: 3081 host.ReloadProgramsCallback.emit(pluginId) 3082 elif action == ENGINE_CALLBACK_RELOAD_ALL: 3083 host.ReloadAllCallback.emit(pluginId) 3084 elif action == ENGINE_CALLBACK_PATCHBAY_CLIENT_ADDED: 3085 host.PatchbayClientAddedCallback.emit(pluginId, value1, value2, valueStr) 3086 elif action == ENGINE_CALLBACK_PATCHBAY_CLIENT_REMOVED: 3087 host.PatchbayClientRemovedCallback.emit(pluginId) 3088 elif action == ENGINE_CALLBACK_PATCHBAY_CLIENT_RENAMED: 3089 host.PatchbayClientRenamedCallback.emit(pluginId, valueStr) 3090 elif action == ENGINE_CALLBACK_PATCHBAY_CLIENT_DATA_CHANGED: 3091 host.PatchbayClientDataChangedCallback.emit(pluginId, value1, value2) 3092 elif action == ENGINE_CALLBACK_PATCHBAY_CLIENT_POSITION_CHANGED: 3093 host.PatchbayClientPositionChangedCallback.emit(pluginId, value1, value2, value3, int(round(valuef))) 3094 elif action == ENGINE_CALLBACK_PATCHBAY_PORT_ADDED: 3095 host.PatchbayPortAddedCallback.emit(pluginId, value1, value2, value3, valueStr) 3096 elif action == ENGINE_CALLBACK_PATCHBAY_PORT_REMOVED: 3097 host.PatchbayPortRemovedCallback.emit(pluginId, value1) 3098 elif action == ENGINE_CALLBACK_PATCHBAY_PORT_CHANGED: 3099 host.PatchbayPortChangedCallback.emit(pluginId, value1, value2, value3, valueStr) 3100 elif action == ENGINE_CALLBACK_PATCHBAY_PORT_GROUP_ADDED: 3101 host.PatchbayPortGroupAddedCallback.emit(pluginId, value1, value2, valueStr) 3102 elif action == ENGINE_CALLBACK_PATCHBAY_PORT_GROUP_REMOVED: 3103 host.PatchbayPortGroupRemovedCallback.emit(pluginId, value1) 3104 elif action == ENGINE_CALLBACK_PATCHBAY_PORT_GROUP_CHANGED: 3105 host.PatchbayPortGroupChangedCallback.emit(pluginId, value1, value2, valueStr) 3106 elif action == ENGINE_CALLBACK_PATCHBAY_CONNECTION_ADDED: 3107 gOut, pOut, gIn, pIn = [int(i) for i in valueStr.split(":")] # FIXME 3108 host.PatchbayConnectionAddedCallback.emit(pluginId, gOut, pOut, gIn, pIn) 3109 elif action == ENGINE_CALLBACK_PATCHBAY_CONNECTION_REMOVED: 3110 host.PatchbayConnectionRemovedCallback.emit(pluginId, value1, value2) 3111 elif action == ENGINE_CALLBACK_ENGINE_STARTED: 3112 host.EngineStartedCallback.emit(pluginId, value1, value2, value3, valuef, valueStr) 3113 elif action == ENGINE_CALLBACK_ENGINE_STOPPED: 3114 host.EngineStoppedCallback.emit() 3115 elif action == ENGINE_CALLBACK_PROCESS_MODE_CHANGED: 3116 host.ProcessModeChangedCallback.emit(value1) 3117 elif action == ENGINE_CALLBACK_TRANSPORT_MODE_CHANGED: 3118 host.TransportModeChangedCallback.emit(value1, valueStr) 3119 elif action == ENGINE_CALLBACK_BUFFER_SIZE_CHANGED: 3120 host.BufferSizeChangedCallback.emit(value1) 3121 elif action == ENGINE_CALLBACK_SAMPLE_RATE_CHANGED: 3122 host.SampleRateChangedCallback.emit(valuef) 3123 elif action == ENGINE_CALLBACK_CANCELABLE_ACTION: 3124 host.CancelableActionCallback.emit(pluginId, bool(value1 != 0), valueStr) 3125 elif action == ENGINE_CALLBACK_PROJECT_LOAD_FINISHED: 3126 host.ProjectLoadFinishedCallback.emit() 3127 elif action == ENGINE_CALLBACK_NSM: 3128 host.NSMCallback.emit(value1, value2, valueStr) 3129 elif action == ENGINE_CALLBACK_IDLE: 3130 QApplication.processEvents() 3131 elif action == ENGINE_CALLBACK_INFO: 3132 host.InfoCallback.emit(valueStr) 3133 elif action == ENGINE_CALLBACK_ERROR: 3134 host.ErrorCallback.emit(valueStr) 3135 elif action == ENGINE_CALLBACK_QUIT: 3136 host.QuitCallback.emit() 3137 elif action == ENGINE_CALLBACK_INLINE_DISPLAY_REDRAW: 3138 host.InlineDisplayRedrawCallback.emit(pluginId) 3139 else: 3140 print("unhandled action", action) 3141 3142# ------------------------------------------------------------------------------------------------------------ 3143# File callback 3144 3145def fileCallback(ptr, action, isDir, title, filter): 3146 title = charPtrToString(title) 3147 filter = charPtrToString(filter) 3148 3149 if action == FILE_CALLBACK_OPEN: 3150 ret, ok = QFileDialog.getOpenFileName(gCarla.gui, title, "", filter) #, QFileDialog.ShowDirsOnly if isDir else 0x0) 3151 elif action == FILE_CALLBACK_SAVE: 3152 ret, ok = QFileDialog.getSaveFileName(gCarla.gui, title, "", filter, QFileDialog.ShowDirsOnly if isDir else 0x0) 3153 else: 3154 ret, ok = ("", "") 3155 3156 # FIXME use ok value, test if it works as expected 3157 if not ret: 3158 return None 3159 3160 # FIXME 3161 global fileRet 3162 fileRet = c_char_p(ret.encode("utf-8")) 3163 retval = cast(byref(fileRet), POINTER(c_uintptr)) 3164 return retval.contents.value 3165 3166# ------------------------------------------------------------------------------------------------------------ 3167# Init host 3168 3169def initHost(initName, libPrefix, isControl, isPlugin, failError, HostClass = None): 3170 pathBinaries, pathResources = getPaths(libPrefix) 3171 3172 # -------------------------------------------------------------------------------------------------------- 3173 # Fail if binary dir is not found 3174 3175 if not os.path.exists(pathBinaries): 3176 if failError: 3177 QMessageBox.critical(None, "Error", "Failed to find the carla binaries, cannot continue") 3178 sys.exit(1) 3179 return 3180 3181 # -------------------------------------------------------------------------------------------------------- 3182 # Check if we should open main lib as local or global 3183 3184 settings = QSafeSettings("falkTX", "Carla2") 3185 3186 loadGlobal = settings.value(CARLA_KEY_EXPERIMENTAL_LOAD_LIB_GLOBAL, CARLA_DEFAULT_EXPERIMENTAL_LOAD_LIB_GLOBAL, bool) 3187 3188 # -------------------------------------------------------------------------------------------------------- 3189 # Set Carla library name 3190 3191 libname = "libcarla_%s2.%s" % ("control" if isControl else "standalone", DLL_EXTENSION) 3192 libname = os.path.join(pathBinaries, libname) 3193 utilsname = os.path.join(pathBinaries, "libcarla_utils.%s" % (DLL_EXTENSION)) 3194 3195 # -------------------------------------------------------------------------------------------------------- 3196 # Print info 3197 3198 if not (gCarla.nogui and isinstance(gCarla.nogui, int)): 3199 print("Carla %s started, status:" % VERSION) 3200 print(" Python version: %s" % sys.version.split(" ",1)[0]) 3201 print(" Qt version: %s" % QT_VERSION_STR) 3202 print(" PyQt version: %s" % PYQT_VERSION_STR) 3203 print(" Binary dir: %s" % pathBinaries) 3204 print(" Resources dir: %s" % pathResources) 3205 3206 # -------------------------------------------------------------------------------------------------------- 3207 # Init host 3208 3209 if failError: 3210 # no try 3211 host = HostClass() if HostClass is not None else CarlaHostQtDLL(libname, loadGlobal) 3212 else: 3213 try: 3214 host = HostClass() if HostClass is not None else CarlaHostQtDLL(libname, loadGlobal) 3215 except: 3216 host = CarlaHostQtNull() 3217 3218 host.isControl = isControl 3219 host.isPlugin = isPlugin 3220 3221 host.set_engine_callback(lambda h,a,p,v1,v2,v3,vf,vs: engineCallback(host,a,p,v1,v2,v3,vf,vs)) 3222 host.set_file_callback(fileCallback) 3223 3224 # If it's a plugin the paths are already set 3225 if not isPlugin: 3226 host.pathBinaries = pathBinaries 3227 host.pathResources = pathResources 3228 host.set_engine_option(ENGINE_OPTION_PATH_BINARIES, 0, pathBinaries) 3229 host.set_engine_option(ENGINE_OPTION_PATH_RESOURCES, 0, pathResources) 3230 3231 if not isControl: 3232 host.nsmOK = host.nsm_init(os.getpid(), initName) 3233 3234 # -------------------------------------------------------------------------------------------------------- 3235 # Init utils 3236 3237 gCarla.utils = CarlaUtils(utilsname) 3238 gCarla.utils.set_process_name(os.path.basename(initName)) 3239 3240 try: 3241 sys.stdout.flush() 3242 except: 3243 pass 3244 3245 sys.stdout = CarlaPrint(False) 3246 sys.stderr = CarlaPrint(True) 3247 sys.excepthook = sys_excepthook 3248 3249 # -------------------------------------------------------------------------------------------------------- 3250 # Done 3251 3252 return host 3253 3254# ------------------------------------------------------------------------------------------------------------ 3255# Load host settings 3256 3257def loadHostSettings(host): 3258 # kdevelop likes this :) 3259 if False: host = CarlaHostNull() 3260 3261 settings = QSafeSettings("falkTX", "Carla2") 3262 3263 host.experimental = settings.value(CARLA_KEY_MAIN_EXPERIMENTAL, CARLA_DEFAULT_MAIN_EXPERIMENTAL, bool) 3264 host.exportLV2 = settings.value(CARLA_KEY_EXPERIMENTAL_EXPORT_LV2, CARLA_DEFAULT_EXPERIMENTAL_LV2_EXPORT, bool) 3265 host.manageUIs = settings.value(CARLA_KEY_ENGINE_MANAGE_UIS, CARLA_DEFAULT_MANAGE_UIS, bool) 3266 host.maxParameters = settings.value(CARLA_KEY_ENGINE_MAX_PARAMETERS, CARLA_DEFAULT_MAX_PARAMETERS, int) 3267 host.resetXruns = settings.value(CARLA_KEY_ENGINE_RESET_XRUNS, CARLA_DEFAULT_RESET_XRUNS, bool) 3268 host.forceStereo = settings.value(CARLA_KEY_ENGINE_FORCE_STEREO, CARLA_DEFAULT_FORCE_STEREO, bool) 3269 host.preferPluginBridges = settings.value(CARLA_KEY_ENGINE_PREFER_PLUGIN_BRIDGES, CARLA_DEFAULT_PREFER_PLUGIN_BRIDGES, bool) 3270 host.preferUIBridges = settings.value(CARLA_KEY_ENGINE_PREFER_UI_BRIDGES, CARLA_DEFAULT_PREFER_UI_BRIDGES, bool) 3271 host.preventBadBehaviour = settings.value(CARLA_KEY_EXPERIMENTAL_PREVENT_BAD_BEHAVIOUR, CARLA_DEFAULT_EXPERIMENTAL_PREVENT_BAD_BEHAVIOUR, bool) 3272 host.showLogs = settings.value(CARLA_KEY_MAIN_SHOW_LOGS, CARLA_DEFAULT_MAIN_SHOW_LOGS, bool) and not WINDOWS 3273 host.showPluginBridges = settings.value(CARLA_KEY_EXPERIMENTAL_PLUGIN_BRIDGES, CARLA_DEFAULT_EXPERIMENTAL_PLUGIN_BRIDGES, bool) 3274 host.showWineBridges = settings.value(CARLA_KEY_EXPERIMENTAL_WINE_BRIDGES, CARLA_DEFAULT_EXPERIMENTAL_WINE_BRIDGES, bool) 3275 host.uiBridgesTimeout = settings.value(CARLA_KEY_ENGINE_UI_BRIDGES_TIMEOUT, CARLA_DEFAULT_UI_BRIDGES_TIMEOUT, int) 3276 host.uisAlwaysOnTop = settings.value(CARLA_KEY_ENGINE_UIS_ALWAYS_ON_TOP, CARLA_DEFAULT_UIS_ALWAYS_ON_TOP, bool) 3277 3278 if host.isPlugin: 3279 return 3280 3281 host.transportExtra = settings.value(CARLA_KEY_ENGINE_TRANSPORT_EXTRA, "", str) 3282 3283 # enums 3284 if host.audioDriverForced is None: 3285 host.transportMode = settings.value(CARLA_KEY_ENGINE_TRANSPORT_MODE, CARLA_DEFAULT_TRANSPORT_MODE, int) 3286 3287 if not host.processModeForced: 3288 host.processMode = settings.value(CARLA_KEY_ENGINE_PROCESS_MODE, CARLA_DEFAULT_PROCESS_MODE, int) 3289 3290 host.nextProcessMode = host.processMode 3291 3292 # -------------------------------------------------------------------------------------------------------- 3293 # fix things if needed 3294 3295 if host.processMode == ENGINE_PROCESS_MODE_MULTIPLE_CLIENTS: 3296 if LADISH_APP_NAME: 3297 print("LADISH detected but using multiple clients (not allowed), forcing single client now") 3298 host.nextProcessMode = host.processMode = ENGINE_PROCESS_MODE_SINGLE_CLIENT 3299 3300 else: 3301 host.transportMode = ENGINE_TRANSPORT_MODE_JACK 3302 3303 if gCarla.nogui: 3304 host.showLogs = False 3305 3306 # -------------------------------------------------------------------------------------------------------- 3307 # run headless host now if nogui option enabled 3308 3309 if gCarla.nogui: 3310 runHostWithoutUI(host) 3311 3312# ------------------------------------------------------------------------------------------------------------ 3313# Set host settings 3314 3315def setHostSettings(host): 3316 # kdevelop likes this :) 3317 if False: host = CarlaHostNull() 3318 3319 host.set_engine_option(ENGINE_OPTION_FORCE_STEREO, host.forceStereo, "") 3320 host.set_engine_option(ENGINE_OPTION_MAX_PARAMETERS, host.maxParameters, "") 3321 host.set_engine_option(ENGINE_OPTION_RESET_XRUNS, host.resetXruns, "") 3322 host.set_engine_option(ENGINE_OPTION_PREFER_PLUGIN_BRIDGES, host.preferPluginBridges, "") 3323 host.set_engine_option(ENGINE_OPTION_PREFER_UI_BRIDGES, host.preferUIBridges, "") 3324 host.set_engine_option(ENGINE_OPTION_PREVENT_BAD_BEHAVIOUR, host.preventBadBehaviour, "") 3325 host.set_engine_option(ENGINE_OPTION_UI_BRIDGES_TIMEOUT, host.uiBridgesTimeout, "") 3326 host.set_engine_option(ENGINE_OPTION_UIS_ALWAYS_ON_TOP, host.uisAlwaysOnTop, "") 3327 3328 if host.isPlugin or host.isRemote or host.is_engine_running(): 3329 return 3330 3331 host.set_engine_option(ENGINE_OPTION_PROCESS_MODE, host.nextProcessMode, "") 3332 host.set_engine_option(ENGINE_OPTION_TRANSPORT_MODE, host.transportMode, host.transportExtra) 3333 host.set_engine_option(ENGINE_OPTION_DEBUG_CONSOLE_OUTPUT, host.showLogs, "") 3334 3335 if not (NSM_URL and host.nsmOK): 3336 host.set_engine_option(ENGINE_OPTION_CLIENT_NAME_PREFIX, 0, gCarla.cnprefix) 3337 3338# ------------------------------------------------------------------------------------------------------------ 3339# Set Engine settings according to carla preferences. Returns selected audio driver. 3340 3341def setEngineSettings(host, oscPort = None): 3342 # kdevelop likes this :) 3343 if False: host = CarlaHostNull() 3344 3345 # -------------------------------------------------------------------------------------------------------- 3346 # do nothing if control 3347 3348 if host.isControl: 3349 return "Control" 3350 3351 # -------------------------------------------------------------------------------------------------------- 3352 3353 settings = QSafeSettings("falkTX", "Carla2") 3354 3355 # -------------------------------------------------------------------------------------------------------- 3356 # main settings 3357 3358 setHostSettings(host) 3359 3360 # -------------------------------------------------------------------------------------------------------- 3361 # file paths 3362 3363 FILE_PATH_AUDIO = settings.value(CARLA_KEY_PATHS_AUDIO, CARLA_DEFAULT_FILE_PATH_AUDIO, list) 3364 FILE_PATH_MIDI = settings.value(CARLA_KEY_PATHS_MIDI, CARLA_DEFAULT_FILE_PATH_MIDI, list) 3365 3366 host.set_engine_option(ENGINE_OPTION_FILE_PATH, FILE_AUDIO, splitter.join(FILE_PATH_AUDIO)) 3367 host.set_engine_option(ENGINE_OPTION_FILE_PATH, FILE_MIDI, splitter.join(FILE_PATH_MIDI)) 3368 3369 # -------------------------------------------------------------------------------------------------------- 3370 # plugin paths 3371 3372 LADSPA_PATH = settings.value(CARLA_KEY_PATHS_LADSPA, CARLA_DEFAULT_LADSPA_PATH, list) 3373 DSSI_PATH = settings.value(CARLA_KEY_PATHS_DSSI, CARLA_DEFAULT_DSSI_PATH, list) 3374 LV2_PATH = settings.value(CARLA_KEY_PATHS_LV2, CARLA_DEFAULT_LV2_PATH, list) 3375 VST2_PATH = settings.value(CARLA_KEY_PATHS_VST2, CARLA_DEFAULT_VST2_PATH, list) 3376 VST3_PATH = settings.value(CARLA_KEY_PATHS_VST3, CARLA_DEFAULT_VST3_PATH, list) 3377 SF2_PATH = settings.value(CARLA_KEY_PATHS_SF2, CARLA_DEFAULT_SF2_PATH, list) 3378 SFZ_PATH = settings.value(CARLA_KEY_PATHS_SFZ, CARLA_DEFAULT_SFZ_PATH, list) 3379 3380 host.set_engine_option(ENGINE_OPTION_PLUGIN_PATH, PLUGIN_LADSPA, splitter.join(LADSPA_PATH)) 3381 host.set_engine_option(ENGINE_OPTION_PLUGIN_PATH, PLUGIN_DSSI, splitter.join(DSSI_PATH)) 3382 host.set_engine_option(ENGINE_OPTION_PLUGIN_PATH, PLUGIN_LV2, splitter.join(LV2_PATH)) 3383 host.set_engine_option(ENGINE_OPTION_PLUGIN_PATH, PLUGIN_VST2, splitter.join(VST2_PATH)) 3384 host.set_engine_option(ENGINE_OPTION_PLUGIN_PATH, PLUGIN_VST3, splitter.join(VST3_PATH)) 3385 host.set_engine_option(ENGINE_OPTION_PLUGIN_PATH, PLUGIN_SF2, splitter.join(SF2_PATH)) 3386 host.set_engine_option(ENGINE_OPTION_PLUGIN_PATH, PLUGIN_SFZ, splitter.join(SFZ_PATH)) 3387 3388 # -------------------------------------------------------------------------------------------------------- 3389 # don't continue if plugin 3390 3391 if host.isPlugin: 3392 return "Plugin" 3393 3394 # -------------------------------------------------------------------------------------------------------- 3395 # osc settings 3396 3397 if oscPort is not None and isinstance(oscPort, int): 3398 oscEnabled = True 3399 portNumTCP = portNumUDP = oscPort 3400 3401 else: 3402 oscEnabled = settings.value(CARLA_KEY_OSC_ENABLED, CARLA_DEFAULT_OSC_ENABLED, bool) 3403 3404 if not settings.value(CARLA_KEY_OSC_TCP_PORT_ENABLED, CARLA_DEFAULT_OSC_TCP_PORT_ENABLED, bool): 3405 portNumTCP = -1 3406 elif settings.value(CARLA_KEY_OSC_TCP_PORT_RANDOM, CARLA_DEFAULT_OSC_TCP_PORT_RANDOM, bool): 3407 portNumTCP = 0 3408 else: 3409 portNumTCP = settings.value(CARLA_KEY_OSC_TCP_PORT_NUMBER, CARLA_DEFAULT_OSC_TCP_PORT_NUMBER, int) 3410 3411 if not settings.value(CARLA_KEY_OSC_UDP_PORT_ENABLED, CARLA_DEFAULT_OSC_UDP_PORT_ENABLED, bool): 3412 portNumUDP = -1 3413 elif settings.value(CARLA_KEY_OSC_UDP_PORT_RANDOM, CARLA_DEFAULT_OSC_UDP_PORT_RANDOM, bool): 3414 portNumUDP = 0 3415 else: 3416 portNumUDP = settings.value(CARLA_KEY_OSC_UDP_PORT_NUMBER, CARLA_DEFAULT_OSC_UDP_PORT_NUMBER, int) 3417 3418 host.set_engine_option(ENGINE_OPTION_OSC_ENABLED, 1 if oscEnabled else 0, "") 3419 host.set_engine_option(ENGINE_OPTION_OSC_PORT_TCP, portNumTCP, "") 3420 host.set_engine_option(ENGINE_OPTION_OSC_PORT_UDP, portNumUDP, "") 3421 3422 # -------------------------------------------------------------------------------------------------------- 3423 # wine settings 3424 3425 optWineExecutable = settings.value(CARLA_KEY_WINE_EXECUTABLE, CARLA_DEFAULT_WINE_EXECUTABLE, str) 3426 optWineAutoPrefix = settings.value(CARLA_KEY_WINE_AUTO_PREFIX, CARLA_DEFAULT_WINE_AUTO_PREFIX, bool) 3427 optWineFallbackPrefix = settings.value(CARLA_KEY_WINE_FALLBACK_PREFIX, CARLA_DEFAULT_WINE_FALLBACK_PREFIX, str) 3428 optWineRtPrioEnabled = settings.value(CARLA_KEY_WINE_RT_PRIO_ENABLED, CARLA_DEFAULT_WINE_RT_PRIO_ENABLED, bool) 3429 optWineBaseRtPrio = settings.value(CARLA_KEY_WINE_BASE_RT_PRIO, CARLA_DEFAULT_WINE_BASE_RT_PRIO, int) 3430 optWineServerRtPrio = settings.value(CARLA_KEY_WINE_SERVER_RT_PRIO, CARLA_DEFAULT_WINE_SERVER_RT_PRIO, int) 3431 3432 host.set_engine_option(ENGINE_OPTION_WINE_EXECUTABLE, 0, optWineExecutable) 3433 host.set_engine_option(ENGINE_OPTION_WINE_AUTO_PREFIX, 1 if optWineAutoPrefix else 0, "") 3434 host.set_engine_option(ENGINE_OPTION_WINE_FALLBACK_PREFIX, 0, os.path.expanduser(optWineFallbackPrefix)) 3435 host.set_engine_option(ENGINE_OPTION_WINE_RT_PRIO_ENABLED, 1 if optWineRtPrioEnabled else 0, "") 3436 host.set_engine_option(ENGINE_OPTION_WINE_BASE_RT_PRIO, optWineBaseRtPrio, "") 3437 host.set_engine_option(ENGINE_OPTION_WINE_SERVER_RT_PRIO, optWineServerRtPrio, "") 3438 3439 # -------------------------------------------------------------------------------------------------------- 3440 # driver and device settings 3441 3442 # driver name 3443 if host.audioDriverForced is not None: 3444 audioDriver = host.audioDriverForced 3445 else: 3446 try: 3447 audioDriver = settings.value(CARLA_KEY_ENGINE_AUDIO_DRIVER, CARLA_DEFAULT_AUDIO_DRIVER, str) 3448 except: 3449 audioDriver = CARLA_DEFAULT_AUDIO_DRIVER 3450 3451 # driver options 3452 audioDevice = settings.value("%s%s/Device" % (CARLA_KEY_ENGINE_DRIVER_PREFIX, audioDriver), "", str) 3453 audioBufferSize = settings.value("%s%s/BufferSize" % (CARLA_KEY_ENGINE_DRIVER_PREFIX, audioDriver), CARLA_DEFAULT_AUDIO_BUFFER_SIZE, int) 3454 audioSampleRate = settings.value("%s%s/SampleRate" % (CARLA_KEY_ENGINE_DRIVER_PREFIX, audioDriver), CARLA_DEFAULT_AUDIO_SAMPLE_RATE, int) 3455 audioTripleBuffer = settings.value("%s%s/TripleBuffer" % (CARLA_KEY_ENGINE_DRIVER_PREFIX, audioDriver), CARLA_DEFAULT_AUDIO_TRIPLE_BUFFER, bool) 3456 3457 # Only setup audio things if engine is not running 3458 if not host.is_engine_running(): 3459 host.set_engine_option(ENGINE_OPTION_AUDIO_DRIVER, 0, audioDriver) 3460 host.set_engine_option(ENGINE_OPTION_AUDIO_DEVICE, 0, audioDevice) 3461 3462 if not audioDriver.startswith("JACK"): 3463 host.set_engine_option(ENGINE_OPTION_AUDIO_BUFFER_SIZE, audioBufferSize, "") 3464 host.set_engine_option(ENGINE_OPTION_AUDIO_SAMPLE_RATE, audioSampleRate, "") 3465 host.set_engine_option(ENGINE_OPTION_AUDIO_TRIPLE_BUFFER, 1 if audioTripleBuffer else 0, "") 3466 3467 # -------------------------------------------------------------------------------------------------------- 3468 # fix things if needed 3469 3470 if audioDriver != "JACK" and host.transportMode == ENGINE_TRANSPORT_MODE_JACK: 3471 host.transportMode = ENGINE_TRANSPORT_MODE_INTERNAL 3472 host.set_engine_option(ENGINE_OPTION_TRANSPORT_MODE, ENGINE_TRANSPORT_MODE_INTERNAL, host.transportExtra) 3473 3474 # -------------------------------------------------------------------------------------------------------- 3475 # return selected driver name 3476 3477 return audioDriver 3478 3479# ------------------------------------------------------------------------------------------------------------ 3480# Run Carla without showing UI 3481 3482def runHostWithoutUI(host): 3483 # kdevelop likes this :) 3484 if False: host = CarlaHostNull() 3485 3486 # -------------------------------------------------------------------------------------------------------- 3487 # Some initial checks 3488 3489 if not gCarla.nogui: 3490 return 3491 3492 projectFile = getInitialProjectFile(True) 3493 3494 if not isinstance(gCarla.nogui, int): 3495 oscPort = None 3496 3497 if not projectFile: 3498 print("Carla no-gui mode can only be used together with a project file.") 3499 sys.exit(1) 3500 3501 else: 3502 oscPort = gCarla.nogui 3503 3504 # -------------------------------------------------------------------------------------------------------- 3505 # Additional imports 3506 3507 from time import sleep 3508 3509 # -------------------------------------------------------------------------------------------------------- 3510 # Init engine 3511 3512 audioDriver = setEngineSettings(host, oscPort) 3513 if not host.engine_init(audioDriver, "Carla"): 3514 print("Engine failed to initialize, possible reasons:\n%s" % host.get_last_error()) 3515 sys.exit(1) 3516 3517 if projectFile and not host.load_project(projectFile): 3518 print("Failed to load selected project file, possible reasons:\n%s" % host.get_last_error()) 3519 host.engine_close() 3520 sys.exit(1) 3521 3522 # -------------------------------------------------------------------------------------------------------- 3523 # Idle 3524 3525 print("Carla ready!") 3526 3527 while host.is_engine_running() and not gCarla.term: 3528 host.engine_idle() 3529 sleep(0.0333) # 30 Hz 3530 3531 # -------------------------------------------------------------------------------------------------------- 3532 # Stop 3533 3534 host.engine_close() 3535 sys.exit(0) 3536 3537# ------------------------------------------------------------------------------------------------------------ 3538