| @@ -3,9 +3,15 @@ | |||||
| <file>16x16/carla.png</file> | <file>16x16/carla.png</file> | ||||
| <file>16x16/carla-control.png</file> | <file>16x16/carla-control.png</file> | ||||
| <file>16x16/add-from-favorites.svgz</file> | |||||
| <file>16x16/add-jack.svgz</file> | |||||
| <file>16x16/application-exit.svgz</file> | <file>16x16/application-exit.svgz</file> | ||||
| <file>16x16/arrow-right.svgz</file> | <file>16x16/arrow-right.svgz</file> | ||||
| <file>16x16/audio-volume-medium.svgz</file> | |||||
| <file>16x16/audio-volume-muted.svgz</file> | |||||
| <file>16x16/balance.svgz</file> | |||||
| <file>16x16/bookmarks.svgz</file> | <file>16x16/bookmarks.svgz</file> | ||||
| <file>16x16/compact.svgz</file> | |||||
| <file>16x16/configure.svgz</file> | <file>16x16/configure.svgz</file> | ||||
| <file>16x16/dialog-cancel.svgz</file> | <file>16x16/dialog-cancel.svgz</file> | ||||
| <file>16x16/dialog-error.svgz</file> | <file>16x16/dialog-error.svgz</file> | ||||
| @@ -16,9 +22,11 @@ | |||||
| <file>16x16/document-open.svgz</file> | <file>16x16/document-open.svgz</file> | ||||
| <file>16x16/document-save.svgz</file> | <file>16x16/document-save.svgz</file> | ||||
| <file>16x16/document-save-as.svgz</file> | <file>16x16/document-save-as.svgz</file> | ||||
| <file>16x16/dry.svgz</file> | |||||
| <file>16x16/edit-clear.svgz</file> | <file>16x16/edit-clear.svgz</file> | ||||
| <file>16x16/edit-delete.svgz</file> | <file>16x16/edit-delete.svgz</file> | ||||
| <file>16x16/edit-rename.svgz</file> | <file>16x16/edit-rename.svgz</file> | ||||
| <file>16x16/emblem-favorite.svgz</file> | |||||
| <file>16x16/list-add.svgz</file> | <file>16x16/list-add.svgz</file> | ||||
| <file>16x16/list-remove.svgz</file> | <file>16x16/list-remove.svgz</file> | ||||
| <file>16x16/media-playback-pause.svgz</file> | <file>16x16/media-playback-pause.svgz</file> | ||||
| @@ -27,8 +35,14 @@ | |||||
| <file>16x16/media-seek-backward.svgz</file> | <file>16x16/media-seek-backward.svgz</file> | ||||
| <file>16x16/media-seek-forward.svgz</file> | <file>16x16/media-seek-forward.svgz</file> | ||||
| <file>16x16/network-connect.svgz</file> | <file>16x16/network-connect.svgz</file> | ||||
| <file>16x16/restore.svgz</file> | |||||
| <file>16x16/style.svgz</file> | |||||
| <file>16x16/system-shutdown.svgz</file> | |||||
| <file>16x16/system-turnon.svgz</file> | |||||
| <file>16x16/view-refresh.svgz</file> | <file>16x16/view-refresh.svgz</file> | ||||
| <file>16x16/view-refresh-purple.svgz</file> | |||||
| <file>16x16/view-sort-ascending.svgz</file> | <file>16x16/view-sort-ascending.svgz</file> | ||||
| <file>16x16/wet.svgz</file> | |||||
| <file>16x16/window-close.svgz</file> | <file>16x16/window-close.svgz</file> | ||||
| <file>16x16/zoom-fit-best.svgz</file> | <file>16x16/zoom-fit-best.svgz</file> | ||||
| <file>16x16/zoom-in.svgz</file> | <file>16x16/zoom-in.svgz</file> | ||||
| @@ -108,17 +108,17 @@ | |||||
| </spacer> | </spacer> | ||||
| </item> | </item> | ||||
| <item> | <item> | ||||
| <widget class="ScalableDial" name="dial_drywet"> | |||||
| <widget class="QWidget" name="dial_drywet"> | |||||
| <property name="minimumSize"> | <property name="minimumSize"> | ||||
| <size> | <size> | ||||
| <width>34</width> | |||||
| <height>34</height> | |||||
| <width>32</width> | |||||
| <height>48</height> | |||||
| </size> | </size> | ||||
| </property> | </property> | ||||
| <property name="maximumSize"> | <property name="maximumSize"> | ||||
| <size> | <size> | ||||
| <width>34</width> | |||||
| <height>34</height> | |||||
| <width>32</width> | |||||
| <height>48</height> | |||||
| </size> | </size> | ||||
| </property> | </property> | ||||
| <property name="contextMenuPolicy"> | <property name="contextMenuPolicy"> | ||||
| @@ -130,17 +130,17 @@ | |||||
| </widget> | </widget> | ||||
| </item> | </item> | ||||
| <item> | <item> | ||||
| <widget class="ScalableDial" name="dial_vol"> | |||||
| <widget class="QWidget" name="dial_vol"> | |||||
| <property name="minimumSize"> | <property name="minimumSize"> | ||||
| <size> | <size> | ||||
| <width>34</width> | |||||
| <height>34</height> | |||||
| <width>32</width> | |||||
| <height>48</height> | |||||
| </size> | </size> | ||||
| </property> | </property> | ||||
| <property name="maximumSize"> | <property name="maximumSize"> | ||||
| <size> | <size> | ||||
| <width>34</width> | |||||
| <height>34</height> | |||||
| <width>32</width> | |||||
| <height>48</height> | |||||
| </size> | </size> | ||||
| </property> | </property> | ||||
| <property name="contextMenuPolicy"> | <property name="contextMenuPolicy"> | ||||
| @@ -152,148 +152,92 @@ | |||||
| </widget> | </widget> | ||||
| </item> | </item> | ||||
| <item> | <item> | ||||
| <widget class="QStackedWidget" name="stackedWidget"> | |||||
| <widget class="QWidget" name="dial_b_left"> | |||||
| <property name="minimumSize"> | |||||
| <size> | |||||
| <width>26</width> | |||||
| <height>40</height> | |||||
| </size> | |||||
| </property> | |||||
| <property name="maximumSize"> | <property name="maximumSize"> | ||||
| <size> | <size> | ||||
| <width>16777215</width> | |||||
| <height>42</height> | |||||
| <width>26</width> | |||||
| <height>40</height> | |||||
| </size> | </size> | ||||
| </property> | </property> | ||||
| <property name="lineWidth"> | |||||
| <number>0</number> | |||||
| <property name="contextMenuPolicy"> | |||||
| <enum>Qt::CustomContextMenu</enum> | |||||
| </property> | </property> | ||||
| <property name="currentIndex"> | |||||
| <number>0</number> | |||||
| <property name="statusTip"> | |||||
| <string>Balance Left (0%)</string> | |||||
| </property> | </property> | ||||
| <widget class="QWidget" name="page_bal"> | |||||
| <layout class="QHBoxLayout" name="horizontalLayout_6"> | |||||
| <property name="spacing"> | |||||
| <number>0</number> | |||||
| </property> | |||||
| <property name="leftMargin"> | |||||
| <number>0</number> | |||||
| </property> | |||||
| <property name="topMargin"> | |||||
| <number>0</number> | |||||
| </property> | |||||
| <property name="rightMargin"> | |||||
| <number>0</number> | |||||
| </property> | |||||
| <property name="bottomMargin"> | |||||
| <number>0</number> | |||||
| </property> | |||||
| <item> | |||||
| <widget class="ScalableDial" name="dial_b_left"> | |||||
| <property name="minimumSize"> | |||||
| <size> | |||||
| <width>26</width> | |||||
| <height>26</height> | |||||
| </size> | |||||
| </property> | |||||
| <property name="maximumSize"> | |||||
| <size> | |||||
| <width>26</width> | |||||
| <height>26</height> | |||||
| </size> | |||||
| </property> | |||||
| <property name="contextMenuPolicy"> | |||||
| <enum>Qt::CustomContextMenu</enum> | |||||
| </property> | |||||
| <property name="statusTip"> | |||||
| <string>Balance Left (0%)</string> | |||||
| </property> | |||||
| </widget> | |||||
| </item> | |||||
| <item> | |||||
| <widget class="ScalableDial" name="dial_b_right"> | |||||
| <property name="minimumSize"> | |||||
| <size> | |||||
| <width>26</width> | |||||
| <height>26</height> | |||||
| </size> | |||||
| </property> | |||||
| <property name="maximumSize"> | |||||
| <size> | |||||
| <width>26</width> | |||||
| <height>26</height> | |||||
| </size> | |||||
| </property> | |||||
| <property name="contextMenuPolicy"> | |||||
| <enum>Qt::CustomContextMenu</enum> | |||||
| </property> | |||||
| <property name="statusTip"> | |||||
| <string>Balance Right (0%)</string> | |||||
| </property> | |||||
| </widget> | |||||
| </item> | |||||
| </layout> | |||||
| </widget> | |||||
| <widget class="QWidget" name="page_pan"> | |||||
| <layout class="QHBoxLayout" name="horizontalLayout_7"> | |||||
| <property name="spacing"> | |||||
| <number>0</number> | |||||
| </property> | |||||
| <property name="leftMargin"> | |||||
| <number>0</number> | |||||
| </property> | |||||
| <property name="topMargin"> | |||||
| <number>0</number> | |||||
| </property> | |||||
| <property name="rightMargin"> | |||||
| <number>0</number> | |||||
| </property> | |||||
| <property name="bottomMargin"> | |||||
| <number>0</number> | |||||
| </property> | |||||
| <item> | |||||
| <widget class="ScalableDial" name="dial_pan"> | |||||
| <property name="minimumSize"> | |||||
| <size> | |||||
| <width>26</width> | |||||
| <height>26</height> | |||||
| </size> | |||||
| </property> | |||||
| <property name="maximumSize"> | |||||
| <size> | |||||
| <width>26</width> | |||||
| <height>26</height> | |||||
| </size> | |||||
| </property> | |||||
| <property name="contextMenuPolicy"> | |||||
| <enum>Qt::CustomContextMenu</enum> | |||||
| </property> | |||||
| <property name="statusTip"> | |||||
| <string>Balance Right (0%)</string> | |||||
| </property> | |||||
| </widget> | |||||
| </item> | |||||
| </layout> | |||||
| </widget> | |||||
| </widget> | </widget> | ||||
| </item> | </item> | ||||
| <item> | <item> | ||||
| <layout class="QVBoxLayout" name="verticalLayout_2"> | |||||
| <property name="spacing"> | |||||
| <number>0</number> | |||||
| <widget class="QWidget" name="dial_b_right"> | |||||
| <property name="minimumSize"> | |||||
| <size> | |||||
| <width>26</width> | |||||
| <height>40</height> | |||||
| </size> | |||||
| </property> | |||||
| <property name="maximumSize"> | |||||
| <size> | |||||
| <width>26</width> | |||||
| <height>40</height> | |||||
| </size> | |||||
| </property> | </property> | ||||
| <item> | |||||
| <widget class="QRadioButton" name="rb_balance"> | |||||
| <property name="text"> | |||||
| <string>Use Balance</string> | |||||
| </property> | |||||
| <property name="checked"> | |||||
| <bool>true</bool> | |||||
| </property> | |||||
| </widget> | |||||
| </item> | |||||
| <item> | |||||
| <widget class="QRadioButton" name="rb_pan"> | |||||
| <property name="text"> | |||||
| <string>Use Panning</string> | |||||
| </property> | |||||
| </widget> | |||||
| </item> | |||||
| </layout> | |||||
| <property name="contextMenuPolicy"> | |||||
| <enum>Qt::CustomContextMenu</enum> | |||||
| </property> | |||||
| <property name="statusTip"> | |||||
| <string>Balance Right (0%)</string> | |||||
| </property> | |||||
| </widget> | |||||
| </item> | |||||
| <item> | |||||
| <widget class="QWidget" name="dial_pan"> | |||||
| <property name="minimumSize"> | |||||
| <size> | |||||
| <width>32</width> | |||||
| <height>48</height> | |||||
| </size> | |||||
| </property> | |||||
| <property name="maximumSize"> | |||||
| <size> | |||||
| <width>32</width> | |||||
| <height>48</height> | |||||
| </size> | |||||
| </property> | |||||
| <property name="contextMenuPolicy"> | |||||
| <enum>Qt::CustomContextMenu</enum> | |||||
| </property> | |||||
| <property name="statusTip"> | |||||
| <string>Left-Right (0%)</string> | |||||
| </property> | |||||
| </widget> | |||||
| </item> | |||||
| <item> | |||||
| <widget class="QWidget" name="dial_forth"> | |||||
| <property name="minimumSize"> | |||||
| <size> | |||||
| <width>32</width> | |||||
| <height>48</height> | |||||
| </size> | |||||
| </property> | |||||
| <property name="maximumSize"> | |||||
| <size> | |||||
| <width>32</width> | |||||
| <height>48</height> | |||||
| </size> | |||||
| </property> | |||||
| <property name="contextMenuPolicy"> | |||||
| <enum>Qt::CustomContextMenu</enum> | |||||
| </property> | |||||
| <property name="statusTip"> | |||||
| <string>Front-Rear (0%)</string> | |||||
| </property> | |||||
| </widget> | |||||
| </item> | </item> | ||||
| <item> | <item> | ||||
| <spacer name="horizontalSpacer_4"> | <spacer name="horizontalSpacer_4"> | ||||
| @@ -310,6 +254,21 @@ | |||||
| </item> | </item> | ||||
| </layout> | </layout> | ||||
| </item> | </item> | ||||
| <item> | |||||
| <widget class="QLabel" name="label_ctrl_channel_2"> | |||||
| <property name="font"> | |||||
| <font> | |||||
| <italic>true</italic> | |||||
| </font> | |||||
| </property> | |||||
| <property name="text"> | |||||
| <string>⚠ L, R are special mixing type.</string> | |||||
| </property> | |||||
| <property name="alignment"> | |||||
| <set>Qt::AlignCenter</set> | |||||
| </property> | |||||
| </widget> | |||||
| </item> | |||||
| </layout> | </layout> | ||||
| </widget> | </widget> | ||||
| </item> | </item> | ||||
| @@ -876,13 +835,6 @@ Plugin Name | |||||
| </item> | </item> | ||||
| </layout> | </layout> | ||||
| </widget> | </widget> | ||||
| <customwidgets> | |||||
| <customwidget> | |||||
| <class>ScalableDial</class> | |||||
| <extends>QDial</extends> | |||||
| <header>widgets/scalabledial.h</header> | |||||
| </customwidget> | |||||
| </customwidgets> | |||||
| <resources> | <resources> | ||||
| <include location="../resources.qrc"/> | <include location="../resources.qrc"/> | ||||
| </resources> | </resources> | ||||
| @@ -474,6 +474,7 @@ | |||||
| <addaction name="act_file_refresh"/> | <addaction name="act_file_refresh"/> | ||||
| <addaction name="act_file_new"/> | <addaction name="act_file_new"/> | ||||
| <addaction name="act_file_open"/> | <addaction name="act_file_open"/> | ||||
| <addaction name="act_file_reload"/> | |||||
| <addaction name="act_file_save"/> | <addaction name="act_file_save"/> | ||||
| <addaction name="act_file_save_as"/> | <addaction name="act_file_save_as"/> | ||||
| <addaction name="separator"/> | <addaction name="separator"/> | ||||
| @@ -508,6 +509,7 @@ | |||||
| <addaction name="separator"/> | <addaction name="separator"/> | ||||
| <addaction name="act_plugins_center"/> | <addaction name="act_plugins_center"/> | ||||
| <addaction name="separator"/> | <addaction name="separator"/> | ||||
| <addaction name="act_plugins_change_skin"/> | |||||
| <addaction name="act_plugins_compact"/> | <addaction name="act_plugins_compact"/> | ||||
| <addaction name="act_plugins_expand"/> | <addaction name="act_plugins_expand"/> | ||||
| </widget> | </widget> | ||||
| @@ -548,6 +550,7 @@ | |||||
| <string>&Settings</string> | <string>&Settings</string> | ||||
| </property> | </property> | ||||
| <addaction name="act_settings_show_toolbar"/> | <addaction name="act_settings_show_toolbar"/> | ||||
| <addaction name="act_settings_show_toolbar_text"/> | |||||
| <addaction name="act_settings_show_meters"/> | <addaction name="act_settings_show_meters"/> | ||||
| <addaction name="act_settings_show_keyboard"/> | <addaction name="act_settings_show_keyboard"/> | ||||
| <addaction name="act_settings_show_side_panel"/> | <addaction name="act_settings_show_side_panel"/> | ||||
| @@ -589,6 +592,7 @@ | |||||
| </attribute> | </attribute> | ||||
| <addaction name="act_file_new"/> | <addaction name="act_file_new"/> | ||||
| <addaction name="act_file_open"/> | <addaction name="act_file_open"/> | ||||
| <addaction name="act_file_reload"/> | |||||
| <addaction name="act_file_save"/> | <addaction name="act_file_save"/> | ||||
| <addaction name="act_file_save_as"/> | <addaction name="act_file_save_as"/> | ||||
| <addaction name="act_file_connect"/> | <addaction name="act_file_connect"/> | ||||
| @@ -600,6 +604,17 @@ | |||||
| <addaction name="act_engine_panic"/> | <addaction name="act_engine_panic"/> | ||||
| <addaction name="separator"/> | <addaction name="separator"/> | ||||
| <addaction name="act_settings_configure"/> | <addaction name="act_settings_configure"/> | ||||
| <addaction name="separator"/> | |||||
| <addaction name="act_plugins_enable"/> | |||||
| <addaction name="act_plugins_disable"/> | |||||
| <addaction name="act_plugins_bypass"/> | |||||
| <addaction name="act_plugins_wet100"/> | |||||
| <addaction name="act_plugins_mute"/> | |||||
| <addaction name="act_plugins_volume100"/> | |||||
| <addaction name="act_plugins_center"/> | |||||
| <addaction name="act_plugins_change_skin"/> | |||||
| <addaction name="act_plugins_compact"/> | |||||
| <addaction name="act_plugins_expand"/> | |||||
| </widget> | </widget> | ||||
| <widget class="QDockWidget" name="dockWidget"> | <widget class="QDockWidget" name="dockWidget"> | ||||
| <property name="sizePolicy"> | <property name="sizePolicy"> | ||||
| @@ -1111,6 +1126,30 @@ | |||||
| <enum>QAction::NoRole</enum> | <enum>QAction::NoRole</enum> | ||||
| </property> | </property> | ||||
| </action> | </action> | ||||
| <action name="act_file_reload"> | |||||
| <property name="icon"> | |||||
| <iconset resource="../resources.qrc"> | |||||
| <normaloff>:/16x16/view-refresh-purple.svgz</normaloff>:/16x16/view-refresh-purple.svgz</iconset> | |||||
| </property> | |||||
| <property name="text"> | |||||
| <string>&Reload (!)</string> | |||||
| </property> | |||||
| <property name="iconText"> | |||||
| <string>Reload (!)</string> | |||||
| </property> | |||||
| <property name="toolTip"> | |||||
| <string>Reload file. CAUTION, non-saved changes will be LOST!</string> | |||||
| </property> | |||||
| <property name="shortcut"> | |||||
| <string>Ctrl+Shift+R</string> | |||||
| </property> | |||||
| <property name="visible"> | |||||
| <bool>false</bool> | |||||
| </property> | |||||
| <property name="menuRole"> | |||||
| <enum>QAction::NoRole</enum> | |||||
| </property> | |||||
| </action> | |||||
| <action name="act_file_save"> | <action name="act_file_save"> | ||||
| <property name="icon"> | <property name="icon"> | ||||
| <iconset resource="../resources.qrc"> | <iconset resource="../resources.qrc"> | ||||
| @@ -1220,6 +1259,10 @@ | |||||
| </property> | </property> | ||||
| </action> | </action> | ||||
| <action name="act_plugins_enable"> | <action name="act_plugins_enable"> | ||||
| <property name="icon"> | |||||
| <iconset resource="../resources.qrc"> | |||||
| <normaloff>:/16x16/system-turnon.svgz</normaloff>:/16x16/system-turnon.svgz</iconset> | |||||
| </property> | |||||
| <property name="text"> | <property name="text"> | ||||
| <string>Enable</string> | <string>Enable</string> | ||||
| </property> | </property> | ||||
| @@ -1228,6 +1271,10 @@ | |||||
| </property> | </property> | ||||
| </action> | </action> | ||||
| <action name="act_plugins_disable"> | <action name="act_plugins_disable"> | ||||
| <property name="icon"> | |||||
| <iconset resource="../resources.qrc"> | |||||
| <normaloff>:/16x16/system-shutdown.svgz</normaloff>:/16x16/system-shutdown.svgz</iconset> | |||||
| </property> | |||||
| <property name="text"> | <property name="text"> | ||||
| <string>Disable</string> | <string>Disable</string> | ||||
| </property> | </property> | ||||
| @@ -1236,6 +1283,10 @@ | |||||
| </property> | </property> | ||||
| </action> | </action> | ||||
| <action name="act_plugins_bypass"> | <action name="act_plugins_bypass"> | ||||
| <property name="icon"> | |||||
| <iconset resource="../resources.qrc"> | |||||
| <normaloff>:/16x16/dry.svgz</normaloff>:/16x16/dry.svgz</iconset> | |||||
| </property> | |||||
| <property name="text"> | <property name="text"> | ||||
| <string>0% Wet (Bypass)</string> | <string>0% Wet (Bypass)</string> | ||||
| </property> | </property> | ||||
| @@ -1244,6 +1295,10 @@ | |||||
| </property> | </property> | ||||
| </action> | </action> | ||||
| <action name="act_plugins_wet100"> | <action name="act_plugins_wet100"> | ||||
| <property name="icon"> | |||||
| <iconset resource="../resources.qrc"> | |||||
| <normaloff>:/16x16/wet.svgz</normaloff>:/16x16/wet.svgz</iconset> | |||||
| </property> | |||||
| <property name="text"> | <property name="text"> | ||||
| <string>100% Wet</string> | <string>100% Wet</string> | ||||
| </property> | </property> | ||||
| @@ -1252,6 +1307,10 @@ | |||||
| </property> | </property> | ||||
| </action> | </action> | ||||
| <action name="act_plugins_mute"> | <action name="act_plugins_mute"> | ||||
| <property name="icon"> | |||||
| <iconset resource="../resources.qrc"> | |||||
| <normaloff>:/16x16/audio-volume-muted.svgz</normaloff>:/16x16/audio-volume-muted.svgz</iconset> | |||||
| </property> | |||||
| <property name="text"> | <property name="text"> | ||||
| <string>0% Volume (Mute)</string> | <string>0% Volume (Mute)</string> | ||||
| </property> | </property> | ||||
| @@ -1260,6 +1319,10 @@ | |||||
| </property> | </property> | ||||
| </action> | </action> | ||||
| <action name="act_plugins_volume100"> | <action name="act_plugins_volume100"> | ||||
| <property name="icon"> | |||||
| <iconset resource="../resources.qrc"> | |||||
| <normaloff>:/16x16/audio-volume-medium.svgz</normaloff>:/16x16/audio-volume-medium.svgz</iconset> | |||||
| </property> | |||||
| <property name="text"> | <property name="text"> | ||||
| <string>100% Volume</string> | <string>100% Volume</string> | ||||
| </property> | </property> | ||||
| @@ -1268,6 +1331,10 @@ | |||||
| </property> | </property> | ||||
| </action> | </action> | ||||
| <action name="act_plugins_center"> | <action name="act_plugins_center"> | ||||
| <property name="icon"> | |||||
| <iconset resource="../resources.qrc"> | |||||
| <normaloff>:/16x16/balance.svgz</normaloff>:/16x16/balance.svgz</iconset> | |||||
| </property> | |||||
| <property name="text"> | <property name="text"> | ||||
| <string>Center Balance</string> | <string>Center Balance</string> | ||||
| </property> | </property> | ||||
| @@ -1435,6 +1502,17 @@ | |||||
| <enum>QAction::NoRole</enum> | <enum>QAction::NoRole</enum> | ||||
| </property> | </property> | ||||
| </action> | </action> | ||||
| <action name="act_settings_show_toolbar_text"> | |||||
| <property name="checkable"> | |||||
| <bool>true</bool> | |||||
| </property> | |||||
| <property name="text"> | |||||
| <string>Show Toolbar Text</string> | |||||
| </property> | |||||
| <property name="menuRole"> | |||||
| <enum>QAction::NoRole</enum> | |||||
| </property> | |||||
| </action> | |||||
| <action name="act_settings_configure"> | <action name="act_settings_configure"> | ||||
| <property name="icon"> | <property name="icon"> | ||||
| <iconset resource="../resources.qrc"> | <iconset resource="../resources.qrc"> | ||||
| @@ -1553,7 +1631,23 @@ | |||||
| <enum>QAction::NoRole</enum> | <enum>QAction::NoRole</enum> | ||||
| </property> | </property> | ||||
| </action> | </action> | ||||
| <action name="act_plugins_change_skin"> | |||||
| <property name="icon"> | |||||
| <iconset resource="../resources.qrc"> | |||||
| <normaloff>:/16x16/style.svgz</normaloff>:/16x16/style.svgz</iconset> | |||||
| </property> | |||||
| <property name="text"> | |||||
| <string>Change &Skin...</string> | |||||
| </property> | |||||
| <property name="menuRole"> | |||||
| <enum>QAction::NoRole</enum> | |||||
| </property> | |||||
| </action> | |||||
| <action name="act_plugins_compact"> | <action name="act_plugins_compact"> | ||||
| <property name="icon"> | |||||
| <iconset resource="../resources.qrc"> | |||||
| <normaloff>:/16x16/compact.svgz</normaloff>:/16x16/compact.svgz</iconset> | |||||
| </property> | |||||
| <property name="text"> | <property name="text"> | ||||
| <string>Compact Slots</string> | <string>Compact Slots</string> | ||||
| </property> | </property> | ||||
| @@ -1562,6 +1656,10 @@ | |||||
| </property> | </property> | ||||
| </action> | </action> | ||||
| <action name="act_plugins_expand"> | <action name="act_plugins_expand"> | ||||
| <property name="icon"> | |||||
| <iconset resource="../resources.qrc"> | |||||
| <normaloff>:/16x16/restore.svgz</normaloff>:/16x16/restore.svgz</iconset> | |||||
| </property> | |||||
| <property name="text"> | <property name="text"> | ||||
| <string>Expand Slots</string> | <string>Expand Slots</string> | ||||
| </property> | </property> | ||||
| @@ -1597,7 +1695,7 @@ | |||||
| <action name="act_plugin_add_jack"> | <action name="act_plugin_add_jack"> | ||||
| <property name="icon"> | <property name="icon"> | ||||
| <iconset resource="../resources.qrc"> | <iconset resource="../resources.qrc"> | ||||
| <normaloff>:/16x16/list-add.svgz</normaloff>:/16x16/list-add.svgz</iconset> | |||||
| <normaloff>:/16x16/add-jack.svgz</normaloff>:/16x16/add-jack.svgz</iconset> | |||||
| </property> | </property> | ||||
| <property name="text"> | <property name="text"> | ||||
| <string>Add &JACK Application...</string> | <string>Add &JACK Application...</string> | ||||
| @@ -511,6 +511,27 @@ | |||||
| </property> | </property> | ||||
| </spacer> | </spacer> | ||||
| </item> | </item> | ||||
| <item row="2" column="0" colspan="3"> | |||||
| <layout class="QHBoxLayout" name="horizontalLayout_28"> | |||||
| <item> | |||||
| <widget class="QLabel" name="label_main_skin_tweaks_1"> | |||||
| <property name="toolTip"> | |||||
| <string>Example: 'Tweak' is for all skins; extra 'skinnameTweak' overrides it for that skin only.</string> | |||||
| </property> | |||||
| <property name="text"> | |||||
| <string>Skin tweaks:</string> | |||||
| </property> | |||||
| </widget> | |||||
| </item> | |||||
| <item> | |||||
| <widget class="QLineEdit" name="le_main_skin_tweaks"> | |||||
| <property name="toolTip"> | |||||
| <string>Example: 'Tweak' is for all skins; extra 'skinnameTweak' overrides it for that skin only.</string> | |||||
| </property> | |||||
| </widget> | |||||
| </item> | |||||
| </layout> | |||||
| </item> | |||||
| </layout> | </layout> | ||||
| </widget> | </widget> | ||||
| </item> | </item> | ||||
| @@ -30,41 +30,117 @@ | |||||
| <item> | <item> | ||||
| <layout class="QVBoxLayout" name="verticalLayout"> | <layout class="QVBoxLayout" name="verticalLayout"> | ||||
| <item> | <item> | ||||
| <widget class="ScalableDial" name="dial_x"> | |||||
| <property name="minimum"> | |||||
| <number>-100</number> | |||||
| <spacer name="verticalSpacer0"> | |||||
| <property name="orientation"> | |||||
| <enum>Qt::Vertical</enum> | |||||
| </property> | |||||
| <property name="sizeType"> | |||||
| <enum>QSizePolicy::Fixed</enum> | |||||
| </property> | |||||
| <property name="minimumSize"> | |||||
| <size> | |||||
| <width>48</width> | |||||
| <height>0</height> | |||||
| </size> | |||||
| </property> | </property> | ||||
| <property name="maximum"> | |||||
| <number>100</number> | |||||
| </spacer> | |||||
| </item> | |||||
| <item> | |||||
| <widget class="QWidget" name="dial_x"> | |||||
| <property name="minimumSize"> | |||||
| <size> | |||||
| <width>48</width> | |||||
| <height>58</height> | |||||
| </size> | |||||
| </property> | |||||
| <property name="maximumSize"> | |||||
| <size> | |||||
| <width>48</width> | |||||
| <height>58</height> | |||||
| </size> | |||||
| </property> | </property> | ||||
| </widget> | </widget> | ||||
| </item> | </item> | ||||
| <item> | <item> | ||||
| <spacer name="verticalSpacer"> | |||||
| <widget class="QWidget" name="dial_out_x"> | |||||
| <property name="minimumSize"> | |||||
| <size> | |||||
| <width>48</width> | |||||
| <height>58</height> | |||||
| </size> | |||||
| </property> | |||||
| <property name="maximumSize"> | |||||
| <size> | |||||
| <width>48</width> | |||||
| <height>58</height> | |||||
| </size> | |||||
| </property> | |||||
| </widget> | |||||
| </item> | |||||
| <item> | |||||
| <spacer name="verticalSpacer1"> | |||||
| <property name="orientation"> | <property name="orientation"> | ||||
| <enum>Qt::Vertical</enum> | <enum>Qt::Vertical</enum> | ||||
| </property> | </property> | ||||
| <property name="sizeType"> | <property name="sizeType"> | ||||
| <enum>QSizePolicy::Fixed</enum> | <enum>QSizePolicy::Fixed</enum> | ||||
| </property> | </property> | ||||
| <property name="sizeHint" stdset="0"> | |||||
| <property name="minimumSize"> | |||||
| <size> | <size> | ||||
| <width>20</width> | |||||
| <height>30</height> | |||||
| <width>48</width> | |||||
| <height>0</height> | |||||
| </size> | </size> | ||||
| </property> | </property> | ||||
| </spacer> | </spacer> | ||||
| </item> | </item> | ||||
| <item> | <item> | ||||
| <widget class="ScalableDial" name="dial_y"> | |||||
| <property name="minimum"> | |||||
| <number>-100</number> | |||||
| <widget class="QWidget" name="dial_y"> | |||||
| <property name="minimumSize"> | |||||
| <size> | |||||
| <width>48</width> | |||||
| <height>58</height> | |||||
| </size> | |||||
| </property> | |||||
| <property name="maximumSize"> | |||||
| <size> | |||||
| <width>48</width> | |||||
| <height>58</height> | |||||
| </size> | |||||
| </property> | </property> | ||||
| <property name="maximum"> | |||||
| <number>100</number> | |||||
| </widget> | |||||
| </item> | |||||
| <item> | |||||
| <widget class="QWidget" name="dial_out_y"> | |||||
| <property name="minimumSize"> | |||||
| <size> | |||||
| <width>48</width> | |||||
| <height>58</height> | |||||
| </size> | |||||
| </property> | |||||
| <property name="maximumSize"> | |||||
| <size> | |||||
| <width>48</width> | |||||
| <height>58</height> | |||||
| </size> | |||||
| </property> | </property> | ||||
| </widget> | </widget> | ||||
| </item> | </item> | ||||
| <item> | |||||
| <spacer name="verticalSpacer2"> | |||||
| <property name="orientation"> | |||||
| <enum>Qt::Vertical</enum> | |||||
| </property> | |||||
| <property name="sizeType"> | |||||
| <enum>QSizePolicy::Fixed</enum> | |||||
| </property> | |||||
| <property name="minimumSize"> | |||||
| <size> | |||||
| <width>48</width> | |||||
| <height>0</height> | |||||
| </size> | |||||
| </property> | |||||
| </spacer> | |||||
| </item> | |||||
| </layout> | </layout> | ||||
| </item> | </item> | ||||
| </layout> | </layout> | ||||
| @@ -947,7 +947,7 @@ public: | |||||
| pData->hints |= PLUGIN_USES_MULTI_PROGS; | pData->hints |= PLUGIN_USES_MULTI_PROGS; | ||||
| if (! kUse16Outs) | if (! kUse16Outs) | ||||
| pData->hints |= PLUGIN_CAN_BALANCE; | |||||
| pData->hints |= PLUGIN_CAN_BALANCE | PLUGIN_CAN_PANNING; | |||||
| // extra plugin hints | // extra plugin hints | ||||
| pData->extraHints = 0x0; | pData->extraHints = 0x0; | ||||
| @@ -1521,7 +1521,7 @@ public: | |||||
| { | { | ||||
| // note - balance not possible with kUse16Outs, so we can safely skip fAudioOutBuffers | // note - balance not possible with kUse16Outs, so we can safely skip fAudioOutBuffers | ||||
| const bool doVolume = (pData->hints & PLUGIN_CAN_VOLUME) != 0 && carla_isNotEqual(pData->postProc.volume, 1.0f); | |||||
| const bool doVolume = (pData->hints & PLUGIN_CAN_VOLUME) != 0 && (carla_isNotEqual(pData->postProc.volume, 1.0f) || carla_isNotEqual(pData->postProc.panning, 0.0f)); | |||||
| const bool doBalance = (pData->hints & PLUGIN_CAN_BALANCE) != 0 && ! (carla_isEqual(pData->postProc.balanceLeft, -1.0f) && carla_isEqual(pData->postProc.balanceRight, 1.0f)); | const bool doBalance = (pData->hints & PLUGIN_CAN_BALANCE) != 0 && ! (carla_isEqual(pData->postProc.balanceLeft, -1.0f) && carla_isEqual(pData->postProc.balanceRight, 1.0f)); | ||||
| float* const oldBufLeft = pData->postProc.extraBuffer; | float* const oldBufLeft = pData->postProc.extraBuffer; | ||||
| @@ -1554,16 +1554,40 @@ public: | |||||
| } | } | ||||
| } | } | ||||
| // Panning | |||||
| // Only decrease of levels, but never increase, unlike 'L, R'. | |||||
| // Note: no any pan processing for Mono. | |||||
| uint32_t q = pData->audioOut.count; | |||||
| float pan = pData->postProc.panning; | |||||
| float vol = pData->postProc.volume; | |||||
| // Pan: Stereo only. | |||||
| if ((pan != 0.0) && (q == 2)) | |||||
| { | |||||
| // left channel(s) reduce when pan to right | |||||
| if ((pan > 0) && (i == 0)) | |||||
| { | |||||
| vol = vol * (1.0 - pan); | |||||
| } | |||||
| // right channel(s) reduce when pan to left | |||||
| else if ((pan < 0) && (i == 1)) | |||||
| { | |||||
| vol = vol * (1.0 + pan); | |||||
| } | |||||
| } | |||||
| // Volume | // Volume | ||||
| if (kUse16Outs) | if (kUse16Outs) | ||||
| { | { | ||||
| for (uint32_t k=0; k < frames; ++k) | for (uint32_t k=0; k < frames; ++k) | ||||
| outBuffer[i][k+timeOffset] = fAudio16Buffers[i][k] * pData->postProc.volume; | |||||
| outBuffer[i][k+timeOffset] = fAudio16Buffers[i][k] * vol; | |||||
| } | } | ||||
| else if (doVolume) | else if (doVolume) | ||||
| { | { | ||||
| for (uint32_t k=0; k < frames; ++k) | for (uint32_t k=0; k < frames; ++k) | ||||
| outBuffer[i][k+timeOffset] *= pData->postProc.volume; | |||||
| outBuffer[i][k+timeOffset] *= vol; | |||||
| } | } | ||||
| } | } | ||||
| @@ -1271,6 +1271,9 @@ public: | |||||
| if (aOuts >= 2 && aOuts % 2 == 0) | if (aOuts >= 2 && aOuts % 2 == 0) | ||||
| pData->hints |= PLUGIN_CAN_BALANCE; | pData->hints |= PLUGIN_CAN_BALANCE; | ||||
| if (aOuts >= 2) | |||||
| pData->hints |= PLUGIN_CAN_PANNING; | |||||
| #endif | #endif | ||||
| // extra plugin hints | // extra plugin hints | ||||
| @@ -2083,7 +2086,11 @@ public: | |||||
| fAudioOutBuffers[i][k] = (fAudioOutBuffers[i][k] * pData->postProc.dryWet) + (bufValue * (1.0f - pData->postProc.dryWet)); | fAudioOutBuffers[i][k] = (fAudioOutBuffers[i][k] * pData->postProc.dryWet) + (bufValue * (1.0f - pData->postProc.dryWet)); | ||||
| } | } | ||||
| } | } | ||||
| } | |||||
| // Do not join this loop with loop above. | |||||
| for (uint32_t i=0; i < pData->audioOut.count; ++i) | |||||
| { | |||||
| // Balance | // Balance | ||||
| if (doBalance) | if (doBalance) | ||||
| { | { | ||||
| @@ -2115,10 +2122,34 @@ public: | |||||
| } | } | ||||
| } | } | ||||
| // Panning | |||||
| // Only decrease of levels, but never increase, unlike 'L, R'. | |||||
| // Note: no pan processing for Mono. | |||||
| uint32_t q = pData->audioOut.count; | |||||
| float pan = pData->postProc.panning; | |||||
| float vol = pData->postProc.volume; | |||||
| // Pan: Stereo, 3 ch (extra rear/bass), or Quadro. | |||||
| if ((pan != 0.0) && ((q == 2) || (q == 3) || (q == 4))) | |||||
| { | |||||
| // left channel(s) reduce when pan to right | |||||
| if ((pan > 0) && ((i == 0) || ((i == 2) && (q == 4)))) | |||||
| { | |||||
| vol = vol * (1.0 - pan); | |||||
| } | |||||
| // right channel(s) reduce when pan to left | |||||
| else if ((pan < 0) && ((i == 1) || (i == 3))) | |||||
| { | |||||
| vol = vol * (1.0 + pan); | |||||
| } | |||||
| } | |||||
| // Volume (and buffer copy) | // Volume (and buffer copy) | ||||
| { | { | ||||
| for (uint32_t k=0; k < frames; ++k) | for (uint32_t k=0; k < frames; ++k) | ||||
| audioOut[i][k+timeOffset] = fAudioOutBuffers[i][k] * pData->postProc.volume; | |||||
| audioOut[i][k+timeOffset] = fAudioOutBuffers[i][k] * vol; | |||||
| } | } | ||||
| } | } | ||||
| @@ -3349,6 +3349,9 @@ public: | |||||
| if (aOuts >= 2 && aOuts % 2 == 0) | if (aOuts >= 2 && aOuts % 2 == 0) | ||||
| pData->hints |= PLUGIN_CAN_BALANCE; | pData->hints |= PLUGIN_CAN_BALANCE; | ||||
| if (aOuts >= 2) | |||||
| pData->hints |= PLUGIN_CAN_PANNING; | |||||
| // extra plugin hints | // extra plugin hints | ||||
| pData->extraHints = 0x0; | pData->extraHints = 0x0; | ||||
| @@ -4684,7 +4687,11 @@ public: | |||||
| fAudioOutBuffers[i][k] = (fAudioOutBuffers[i][k] * pData->postProc.dryWet) + (bufValue * (1.0f - pData->postProc.dryWet)); | fAudioOutBuffers[i][k] = (fAudioOutBuffers[i][k] * pData->postProc.dryWet) + (bufValue * (1.0f - pData->postProc.dryWet)); | ||||
| } | } | ||||
| } | } | ||||
| } | |||||
| // Do not join this loop with loop above. | |||||
| for (uint32_t i=0; i < pData->audioOut.count; ++i) | |||||
| { | |||||
| // Balance | // Balance | ||||
| if (doBalance) | if (doBalance) | ||||
| { | { | ||||
| @@ -4716,10 +4723,34 @@ public: | |||||
| } | } | ||||
| } | } | ||||
| // Panning | |||||
| // Only decrease of levels, but never increase, unlike 'L, R'. | |||||
| // Note: no pan processing for Mono. | |||||
| uint32_t q = pData->audioOut.count; | |||||
| float pan = pData->postProc.panning; | |||||
| float vol = pData->postProc.volume; | |||||
| // Pan: Stereo, 3 ch (extra rear/bass), or Quadro. | |||||
| if ((pan != 0.0) && ((q == 2) || (q == 3) || (q == 4))) | |||||
| { | |||||
| // left channel(s) reduce when pan to right | |||||
| if ((pan > 0) && ((i == 0) || ((i == 2) && (q == 4)))) | |||||
| { | |||||
| vol = vol * (1.0 - pan); | |||||
| } | |||||
| // right channel(s) reduce when pan to left | |||||
| else if ((pan < 0) && ((i == 1) || (i == 3))) | |||||
| { | |||||
| vol = vol * (1.0 + pan); | |||||
| } | |||||
| } | |||||
| // Volume (and buffer copy) | // Volume (and buffer copy) | ||||
| { | { | ||||
| for (uint32_t k=0; k < frames; ++k) | for (uint32_t k=0; k < frames; ++k) | ||||
| audioOut[i][k+timeOffset] = fAudioOutBuffers[i][k] * pData->postProc.volume; | |||||
| audioOut[i][k+timeOffset] = fAudioOutBuffers[i][k] * vol; | |||||
| } | } | ||||
| } | } | ||||
| } // End of Post-processing | } // End of Post-processing | ||||
| @@ -1405,6 +1405,9 @@ public: | |||||
| if (aOuts >= 2 && aOuts % 2 == 0) | if (aOuts >= 2 && aOuts % 2 == 0) | ||||
| pData->hints |= PLUGIN_CAN_BALANCE; | pData->hints |= PLUGIN_CAN_BALANCE; | ||||
| if (aOuts >= 2) | |||||
| pData->hints |= PLUGIN_CAN_PANNING; | |||||
| // native plugin hints | // native plugin hints | ||||
| if (fDescriptor->hints & NATIVE_PLUGIN_IS_RTSAFE) | if (fDescriptor->hints & NATIVE_PLUGIN_IS_RTSAFE) | ||||
| pData->hints |= PLUGIN_IS_RTSAFE; | pData->hints |= PLUGIN_IS_RTSAFE; | ||||
| @@ -2369,7 +2372,7 @@ public: | |||||
| float bufValue; | float bufValue; | ||||
| float* const oldBufLeft = pData->postProc.extraBuffer; | float* const oldBufLeft = pData->postProc.extraBuffer; | ||||
| for (; i < pData->audioOut.count; ++i) | |||||
| for (uint32_t i=0; i < pData->audioOut.count; ++i) | |||||
| { | { | ||||
| // Dry/Wet | // Dry/Wet | ||||
| if (doDryWet) | if (doDryWet) | ||||
| @@ -2380,7 +2383,11 @@ public: | |||||
| fAudioAndCvOutBuffers[i][k] = (fAudioAndCvOutBuffers[i][k] * pData->postProc.dryWet) + (bufValue * (1.0f - pData->postProc.dryWet)); | fAudioAndCvOutBuffers[i][k] = (fAudioAndCvOutBuffers[i][k] * pData->postProc.dryWet) + (bufValue * (1.0f - pData->postProc.dryWet)); | ||||
| } | } | ||||
| } | } | ||||
| } | |||||
| // Do not join this loop with loop above. | |||||
| for (uint32_t i=0; i < pData->audioOut.count; ++i) | |||||
| { | |||||
| // Balance | // Balance | ||||
| if (doBalance) | if (doBalance) | ||||
| { | { | ||||
| @@ -2412,10 +2419,34 @@ public: | |||||
| } | } | ||||
| } | } | ||||
| // Panning | |||||
| // Only decrease of levels, but never increase, unlike 'L, R'. | |||||
| // Note: no pan processing for Mono. | |||||
| uint32_t q = pData->audioOut.count; | |||||
| float pan = pData->postProc.panning; | |||||
| float vol = pData->postProc.volume; | |||||
| // Pan: Stereo, 3 ch (extra rear/bass), or Quadro. | |||||
| if ((pan != 0.0) && ((q == 2) || (q == 3) || (q == 4))) | |||||
| { | |||||
| // left channel(s) reduce when pan to right | |||||
| if ((pan > 0) && ((i == 0) || ((i == 2) && (q == 4)))) | |||||
| { | |||||
| vol = vol * (1.0 - pan); | |||||
| } | |||||
| // right channel(s) reduce when pan to left | |||||
| else if ((pan < 0) && ((i == 1) || (i == 3))) | |||||
| { | |||||
| vol = vol * (1.0 + pan); | |||||
| } | |||||
| } | |||||
| // Volume (and buffer copy) | // Volume (and buffer copy) | ||||
| { | { | ||||
| for (uint32_t k=0; k < frames; ++k) | for (uint32_t k=0; k < frames; ++k) | ||||
| audioOut[i][k+timeOffset] = fAudioAndCvOutBuffers[i][k] * pData->postProc.volume; | |||||
| audioOut[i][k+timeOffset] = fAudioAndCvOutBuffers[i][k] * vol; | |||||
| } | } | ||||
| } | } | ||||
| @@ -310,6 +310,16 @@ PARAMETER_CAN_BE_CV_CONTROLLED = 0x800 | |||||
| # @note only valid for parameter inputs. | # @note only valid for parameter inputs. | ||||
| PARAMETER_IS_NOT_SAVED = 0x1000 | PARAMETER_IS_NOT_SAVED = 0x1000 | ||||
| # Human readable labels for 24 decoded bits (currently for XRay tab of Edit dialog). | |||||
| # Are some hints can exceed 2^24 ? | |||||
| parameterHintsText = ( | |||||
| "IS_BOOLEAN", "IS_INTEGER", "IS_LOGARITHMIC", "n/a", | |||||
| "IS_ENABLED", "IS_AUTOMATABLE", "IS_READ_ONLY", "n/a", | |||||
| "USES_SAMPLERATE", "USES_SCALEPOINTS", "USES_CUSTOM_TEXT", "CAN_BE_CV_CONTROLLED", | |||||
| "IS_NOT_SAVED", "n/a", "n/a", "n/a", | |||||
| "n/a", "n/a", "n/a", "n/a", | |||||
| "n/a", "n/a", "n/a", "n/a", ) | |||||
| # --------------------------------------------------------------------------------------------------------------------- | # --------------------------------------------------------------------------------------------------------------------- | ||||
| # Mapped Parameter Flags | # Mapped Parameter Flags | ||||
| # Various flags for parameter mappings. | # Various flags for parameter mappings. | ||||
| @@ -54,6 +54,7 @@ if qt_config == 5: | |||||
| QListWidgetItem, | QListWidgetItem, | ||||
| QGraphicsView, | QGraphicsView, | ||||
| QMainWindow, | QMainWindow, | ||||
| QToolButton, | |||||
| ) | ) | ||||
| elif qt_config == 6: | elif qt_config == 6: | ||||
| @@ -86,6 +87,7 @@ elif qt_config == 6: | |||||
| QListWidgetItem, | QListWidgetItem, | ||||
| QGraphicsView, | QGraphicsView, | ||||
| QMainWindow, | QMainWindow, | ||||
| QToolButton, | |||||
| ) | ) | ||||
| # ------------------------------------------------------------------------------------------------------------ | # ------------------------------------------------------------------------------------------------------------ | ||||
| @@ -101,6 +103,7 @@ from carla_shared import * | |||||
| from carla_settings import * | from carla_settings import * | ||||
| from carla_utils import * | from carla_utils import * | ||||
| from carla_widgets import * | from carla_widgets import * | ||||
| from carla_skin import * | |||||
| from patchcanvas import patchcanvas | from patchcanvas import patchcanvas | ||||
| from widgets.digitalpeakmeter import DigitalPeakMeter | from widgets.digitalpeakmeter import DigitalPeakMeter | ||||
| @@ -221,6 +224,8 @@ class HostWindow(QMainWindow): | |||||
| self.fOscAddressTCP = "" | self.fOscAddressTCP = "" | ||||
| self.fOscAddressUDP = "" | self.fOscAddressUDP = "" | ||||
| self.slowTimer = 0 | |||||
| if CARLA_OS_MAC: | if CARLA_OS_MAC: | ||||
| self.fMacClosingHelper = True | self.fMacClosingHelper = True | ||||
| @@ -270,6 +275,8 @@ class HostWindow(QMainWindow): | |||||
| self.fWithCanvas = withCanvas | self.fWithCanvas = withCanvas | ||||
| self.fTweaks = {} | |||||
| # ---------------------------------------------------------------------------------------------------- | # ---------------------------------------------------------------------------------------------------- | ||||
| # Internal stuff (logs) | # Internal stuff (logs) | ||||
| @@ -293,6 +300,7 @@ class HostWindow(QMainWindow): | |||||
| if self.host.isControl: | if self.host.isControl: | ||||
| self.ui.act_file_new.setVisible(False) | self.ui.act_file_new.setVisible(False) | ||||
| self.ui.act_file_open.setVisible(False) | self.ui.act_file_open.setVisible(False) | ||||
| self.ui.act_file_reload.setVisible(False) | |||||
| self.ui.act_file_save_as.setVisible(False) | self.ui.act_file_save_as.setVisible(False) | ||||
| self.ui.tabUtils.removeTab(0) | self.ui.tabUtils.removeTab(0) | ||||
| else: | else: | ||||
| @@ -319,10 +327,12 @@ class HostWindow(QMainWindow): | |||||
| self.ui.act_file_new.setEnabled(False) | self.ui.act_file_new.setEnabled(False) | ||||
| self.ui.act_file_open.setEnabled(False) | self.ui.act_file_open.setEnabled(False) | ||||
| self.ui.act_file_reload.setEnabled(False) | |||||
| self.ui.act_file_save.setEnabled(False) | self.ui.act_file_save.setEnabled(False) | ||||
| self.ui.act_file_save_as.setEnabled(False) | self.ui.act_file_save_as.setEnabled(False) | ||||
| self.ui.act_engine_stop.setEnabled(False) | self.ui.act_engine_stop.setEnabled(False) | ||||
| self.ui.act_plugin_remove_all.setEnabled(False) | |||||
| # self.ui.act_plugin_remove_all.setEnabled(False) | |||||
| self.setMenuMacrosEnabled(False) | |||||
| self.ui.act_canvas_show_internal.setChecked(False) | self.ui.act_canvas_show_internal.setChecked(False) | ||||
| self.ui.act_canvas_show_internal.setVisible(False) | self.ui.act_canvas_show_internal.setVisible(False) | ||||
| @@ -503,6 +513,7 @@ class HostWindow(QMainWindow): | |||||
| self.ui.act_file_refresh.setIcon(getIcon('view-refresh', 16, 'svgz')) | self.ui.act_file_refresh.setIcon(getIcon('view-refresh', 16, 'svgz')) | ||||
| self.ui.act_file_new.setIcon(getIcon('document-new', 16, 'svgz')) | self.ui.act_file_new.setIcon(getIcon('document-new', 16, 'svgz')) | ||||
| self.ui.act_file_open.setIcon(getIcon('document-open', 16, 'svgz')) | self.ui.act_file_open.setIcon(getIcon('document-open', 16, 'svgz')) | ||||
| self.ui.act_file_reload.setIcon(getIcon('view-refresh-purple', 16, 'svgz')) | |||||
| self.ui.act_file_save.setIcon(getIcon('document-save', 16, 'svgz')) | self.ui.act_file_save.setIcon(getIcon('document-save', 16, 'svgz')) | ||||
| self.ui.act_file_save_as.setIcon(getIcon('document-save-as', 16, 'svgz')) | self.ui.act_file_save_as.setIcon(getIcon('document-save-as', 16, 'svgz')) | ||||
| self.ui.act_file_quit.setIcon(getIcon('application-exit', 16, 'svgz')) | self.ui.act_file_quit.setIcon(getIcon('application-exit', 16, 'svgz')) | ||||
| @@ -511,8 +522,18 @@ class HostWindow(QMainWindow): | |||||
| self.ui.act_engine_panic.setIcon(getIcon('dialog-warning', 16, 'svgz')) | self.ui.act_engine_panic.setIcon(getIcon('dialog-warning', 16, 'svgz')) | ||||
| self.ui.act_engine_config.setIcon(getIcon('configure', 16, 'svgz')) | self.ui.act_engine_config.setIcon(getIcon('configure', 16, 'svgz')) | ||||
| self.ui.act_plugin_add.setIcon(getIcon('list-add', 16, 'svgz')) | self.ui.act_plugin_add.setIcon(getIcon('list-add', 16, 'svgz')) | ||||
| self.ui.act_plugin_add_jack.setIcon(getIcon('list-add', 16, 'svgz')) | |||||
| self.ui.act_plugin_add_jack.setIcon(getIcon('add-jack', 16, 'svgz')) | |||||
| self.ui.act_plugin_remove_all.setIcon(getIcon('edit-delete', 16, 'svgz')) | self.ui.act_plugin_remove_all.setIcon(getIcon('edit-delete', 16, 'svgz')) | ||||
| self.ui.act_plugins_enable.setIcon(getIcon('system-turnon', 16, 'svgz')) | |||||
| self.ui.act_plugins_disable.setIcon(getIcon('system-shutdown', 16, 'svgz')) | |||||
| self.ui.act_plugins_bypass.setIcon(getIcon('dry', 16, 'svgz')) | |||||
| self.ui.act_plugins_wet100.setIcon(getIcon('wet', 16, 'svgz')) | |||||
| self.ui.act_plugins_mute.setIcon(getIcon('audio-volume-muted', 16, 'svgz')) | |||||
| self.ui.act_plugins_volume100.setIcon(getIcon('audio-volume-medium', 16, 'svgz')) | |||||
| self.ui.act_plugins_center.setIcon(getIcon('balance', 16, 'svgz')) | |||||
| self.ui.act_plugins_change_skin.setIcon(getIcon('skin', 16, 'svgz')) | |||||
| self.ui.act_plugins_compact.setIcon(getIcon('compact', 16, 'svgz')) | |||||
| self.ui.act_plugins_expand.setIcon(getIcon('restore', 16, 'svgz')) | |||||
| self.ui.act_canvas_arrange.setIcon(getIcon('view-sort-ascending', 16, 'svgz')) | self.ui.act_canvas_arrange.setIcon(getIcon('view-sort-ascending', 16, 'svgz')) | ||||
| self.ui.act_canvas_refresh.setIcon(getIcon('view-refresh', 16, 'svgz')) | self.ui.act_canvas_refresh.setIcon(getIcon('view-refresh', 16, 'svgz')) | ||||
| self.ui.act_canvas_zoom_fit.setIcon(getIcon('zoom-fit-best', 16, 'svgz')) | self.ui.act_canvas_zoom_fit.setIcon(getIcon('zoom-fit-best', 16, 'svgz')) | ||||
| @@ -534,6 +555,7 @@ class HostWindow(QMainWindow): | |||||
| self.ui.act_file_new.triggered.connect(self.slot_fileNew) | self.ui.act_file_new.triggered.connect(self.slot_fileNew) | ||||
| self.ui.act_file_open.triggered.connect(self.slot_fileOpen) | self.ui.act_file_open.triggered.connect(self.slot_fileOpen) | ||||
| self.ui.act_file_reload.triggered.connect(self.slot_fileReload) | |||||
| self.ui.act_file_save.triggered.connect(self.slot_fileSave) | self.ui.act_file_save.triggered.connect(self.slot_fileSave) | ||||
| self.ui.act_file_save_as.triggered.connect(self.slot_fileSaveAs) | self.ui.act_file_save_as.triggered.connect(self.slot_fileSaveAs) | ||||
| @@ -553,10 +575,12 @@ class HostWindow(QMainWindow): | |||||
| self.ui.act_plugins_wet100.triggered.connect(self.slot_pluginsWet100) | self.ui.act_plugins_wet100.triggered.connect(self.slot_pluginsWet100) | ||||
| self.ui.act_plugins_bypass.triggered.connect(self.slot_pluginsBypass) | self.ui.act_plugins_bypass.triggered.connect(self.slot_pluginsBypass) | ||||
| self.ui.act_plugins_center.triggered.connect(self.slot_pluginsCenter) | self.ui.act_plugins_center.triggered.connect(self.slot_pluginsCenter) | ||||
| self.ui.act_plugins_change_skin.triggered.connect(self.slot_pluginsChangeSkin) | |||||
| self.ui.act_plugins_compact.triggered.connect(self.slot_pluginsCompact) | self.ui.act_plugins_compact.triggered.connect(self.slot_pluginsCompact) | ||||
| self.ui.act_plugins_expand.triggered.connect(self.slot_pluginsExpand) | self.ui.act_plugins_expand.triggered.connect(self.slot_pluginsExpand) | ||||
| self.ui.act_settings_show_toolbar.toggled.connect(self.slot_showToolbar) | self.ui.act_settings_show_toolbar.toggled.connect(self.slot_showToolbar) | ||||
| self.ui.act_settings_show_toolbar_text.toggled.connect(self.slot_showToolbarText) | |||||
| self.ui.act_settings_show_meters.toggled.connect(self.slot_showCanvasMeters) | self.ui.act_settings_show_meters.toggled.connect(self.slot_showCanvasMeters) | ||||
| self.ui.act_settings_show_keyboard.toggled.connect(self.slot_showCanvasKeyboard) | self.ui.act_settings_show_keyboard.toggled.connect(self.slot_showCanvasKeyboard) | ||||
| self.ui.act_settings_show_side_panel.toggled.connect(self.slot_showSidePanel) | self.ui.act_settings_show_side_panel.toggled.connect(self.slot_showSidePanel) | ||||
| @@ -738,7 +762,7 @@ class HostWindow(QMainWindow): | |||||
| self.host.set_custom_data(pluginId, CUSTOM_DATA_TYPE_PROPERTY, "CarlaColor", colorStr) | self.host.set_custom_data(pluginId, CUSTOM_DATA_TYPE_PROPERTY, "CarlaColor", colorStr) | ||||
| pitem.recreateWidget(newColor = color) | pitem.recreateWidget(newColor = color) | ||||
| def changePluginSkin(self, pluginId, skin): | |||||
| def changePluginSkin(self, pluginId, skin, color = None): | |||||
| if pluginId > self.fPluginCount: | if pluginId > self.fPluginCount: | ||||
| return | return | ||||
| @@ -748,7 +772,9 @@ class HostWindow(QMainWindow): | |||||
| return | return | ||||
| self.host.set_custom_data(pluginId, CUSTOM_DATA_TYPE_PROPERTY, "CarlaSkin", skin) | self.host.set_custom_data(pluginId, CUSTOM_DATA_TYPE_PROPERTY, "CarlaSkin", skin) | ||||
| if skin not in ("default","rncbc","presets","mpresets"): | |||||
| if color is not None: | |||||
| pitem.recreateWidget(newSkin = skin, newColor = color) | |||||
| elif skin not in ("default","rncbc","presets","mpresets"): | |||||
| pitem.recreateWidget(newSkin = skin, newColor = (255,255,255)) | pitem.recreateWidget(newSkin = skin, newColor = (255,255,255)) | ||||
| else: | else: | ||||
| pitem.recreateWidget(newSkin = skin) | pitem.recreateWidget(newSkin = skin) | ||||
| @@ -935,6 +961,16 @@ class HostWindow(QMainWindow): | |||||
| self.loadProjectNow() | self.loadProjectNow() | ||||
| self.fProjectFilename = filenameOld | self.fProjectFilename = filenameOld | ||||
| if self.fTweaks.get('ShowReload', 0): | |||||
| self.ui.act_file_reload.setEnabled(True) | |||||
| self.ui.act_file_reload.setVisible(True) | |||||
| @pyqtSlot() | |||||
| def slot_fileReload(self): | |||||
| if not (self.fProjectFilename == ""): | |||||
| self.pluginRemoveAll() | |||||
| self.loadProjectNow() | |||||
| @pyqtSlot() | @pyqtSlot() | ||||
| def slot_fileSave(self, saveAs=False): | def slot_fileSave(self, saveAs=False): | ||||
| if self.fProjectFilename and not saveAs: | if self.fProjectFilename and not saveAs: | ||||
| @@ -1177,6 +1213,7 @@ class HostWindow(QMainWindow): | |||||
| if self.host.isPlugin or not self.fSessionManagerName: | if self.host.isPlugin or not self.fSessionManagerName: | ||||
| self.ui.act_file_open.setEnabled(False) | self.ui.act_file_open.setEnabled(False) | ||||
| self.ui.act_file_reload.setEnabled(False) | |||||
| self.ui.act_file_save_as.setEnabled(False) | self.ui.act_file_save_as.setEnabled(False) | ||||
| @pyqtSlot(int, str) | @pyqtSlot(int, str) | ||||
| @@ -1226,7 +1263,8 @@ class HostWindow(QMainWindow): | |||||
| # Plugins | # Plugins | ||||
| def removeAllPlugins(self): | def removeAllPlugins(self): | ||||
| self.ui.act_plugin_remove_all.setEnabled(False) | |||||
| # self.ui.act_plugin_remove_all.setEnabled(False) | |||||
| self.setMenuMacrosEnabled(False) | |||||
| patchcanvas.handleAllPluginsRemoved() | patchcanvas.handleAllPluginsRemoved() | ||||
| while self.ui.listWidget.takeItem(0): | while self.ui.listWidget.takeItem(0): | ||||
| @@ -1343,6 +1381,12 @@ class HostWindow(QMainWindow): | |||||
| act = fmenu.addAction(p['name']) | act = fmenu.addAction(p['name']) | ||||
| act.setData(p) | act.setData(p) | ||||
| act.triggered.connect(self.slot_favoritePluginAdd) | act.triggered.connect(self.slot_favoritePluginAdd) | ||||
| if self.fSavedSettings[CARLA_KEY_MAIN_SYSTEM_ICONS]: | |||||
| fmenu.setIcon(getIcon('add-from-favorites', 16, 'svgz')) | |||||
| else: | |||||
| fmenu.setIcon(QIcon(":/16x16/add-from-favorites.svgz")) | |||||
| menu.addMenu(fmenu) | menu.addMenu(fmenu) | ||||
| menu.addAction(self.ui.act_plugin_remove_all) | menu.addAction(self.ui.act_plugin_remove_all) | ||||
| @@ -1359,6 +1403,7 @@ class HostWindow(QMainWindow): | |||||
| menu.addSeparator() | menu.addSeparator() | ||||
| menu.addAction(self.ui.act_plugins_center) | menu.addAction(self.ui.act_plugins_center) | ||||
| menu.addSeparator() | menu.addSeparator() | ||||
| menu.addAction(self.ui.act_plugins_change_skin) | |||||
| menu.addAction(self.ui.act_plugins_compact) | menu.addAction(self.ui.act_plugins_compact) | ||||
| menu.addAction(self.ui.act_plugins_expand) | menu.addAction(self.ui.act_plugins_expand) | ||||
| @@ -1454,7 +1499,7 @@ class HostWindow(QMainWindow): | |||||
| if pitem is None: | if pitem is None: | ||||
| break | break | ||||
| pitem.getWidget().setInternalParameter(PLUGIN_CAN_VOLUME, 1.0) | |||||
| pitem.getWidget().setInternalParameter(PARAMETER_VOLUME, 1.0) | |||||
| @pyqtSlot() | @pyqtSlot() | ||||
| def slot_pluginsMute(self): | def slot_pluginsMute(self): | ||||
| @@ -1465,7 +1510,7 @@ class HostWindow(QMainWindow): | |||||
| if pitem is None: | if pitem is None: | ||||
| break | break | ||||
| pitem.getWidget().setInternalParameter(PLUGIN_CAN_VOLUME, 0.0) | |||||
| pitem.getWidget().setInternalParameter(PARAMETER_VOLUME, 0.0) | |||||
| @pyqtSlot() | @pyqtSlot() | ||||
| def slot_pluginsWet100(self): | def slot_pluginsWet100(self): | ||||
| @@ -1476,7 +1521,7 @@ class HostWindow(QMainWindow): | |||||
| if pitem is None: | if pitem is None: | ||||
| break | break | ||||
| pitem.getWidget().setInternalParameter(PLUGIN_CAN_DRYWET, 1.0) | |||||
| pitem.getWidget().setInternalParameter(PARAMETER_DRYWET, 1.0) | |||||
| @pyqtSlot() | @pyqtSlot() | ||||
| def slot_pluginsBypass(self): | def slot_pluginsBypass(self): | ||||
| @@ -1487,7 +1532,7 @@ class HostWindow(QMainWindow): | |||||
| if pitem is None: | if pitem is None: | ||||
| break | break | ||||
| pitem.getWidget().setInternalParameter(PLUGIN_CAN_DRYWET, 0.0) | |||||
| pitem.getWidget().setInternalParameter(PARAMETER_DRYWET, 0.0) | |||||
| @pyqtSlot() | @pyqtSlot() | ||||
| def slot_pluginsCenter(self): | def slot_pluginsCenter(self): | ||||
| @@ -1502,6 +1547,38 @@ class HostWindow(QMainWindow): | |||||
| pitem.getWidget().setInternalParameter(PARAMETER_BALANCE_RIGHT, 1.0) | pitem.getWidget().setInternalParameter(PARAMETER_BALANCE_RIGHT, 1.0) | ||||
| pitem.getWidget().setInternalParameter(PARAMETER_PANNING, 0.0) | pitem.getWidget().setInternalParameter(PARAMETER_PANNING, 0.0) | ||||
| @pyqtSlot() | |||||
| def slot_pluginsChangeSkin(self): | |||||
| skin = QInputDialog.getItem(self, self.tr("Change Skin"), self.tr("Change Skin to:"), skinList, 0, False) | |||||
| if not all(skin): | |||||
| return | |||||
| if skin[0][:4] in ("calf", "clas", "zynf"): # These are non-colorable | |||||
| for pluginId in range(self.fPluginCount): | |||||
| gCarla.gui.changePluginSkin(pluginId, skin[0]) | |||||
| return | |||||
| reColor = QInputDialog.getItem(self, self.tr("Change Color"), self.tr("Change Color mode:"), ['Follow Rules / As Is', 'Random'], 0, False) | |||||
| if not all(reColor): | |||||
| return | |||||
| color = None | |||||
| if skin[0].startswith("3ba"): # These are dark tinted, need enlight. | |||||
| color = (254,255,255) # If not random | |||||
| luma = 0.5 | |||||
| sat = 0.5 | |||||
| elif skin[0].startswith("ope"): # These are dark tinted, need enlight. | |||||
| luma = 0.5 | |||||
| sat = 1.0 | |||||
| else: | |||||
| luma = 0.125 | |||||
| sat = 0.25 | |||||
| for pluginId in range(self.fPluginCount): | |||||
| if reColor[0][:2] == 'Ra': | |||||
| color = QColor.fromHslF(random.random(), sat, luma, 1).getRgb()[0:3] | |||||
| gCarla.gui.changePluginSkin(pluginId, skin[0], color) | |||||
| @pyqtSlot() | @pyqtSlot() | ||||
| def slot_pluginsCompact(self): | def slot_pluginsCompact(self): | ||||
| for pitem in self.fPluginList: | for pitem in self.fPluginList: | ||||
| @@ -1531,11 +1608,24 @@ class HostWindow(QMainWindow): | |||||
| self.fPluginList.append(pitem) | self.fPluginList.append(pitem) | ||||
| self.fPluginCount += 1 | self.fPluginCount += 1 | ||||
| self.ui.act_plugin_remove_all.setEnabled(self.fPluginCount > 0) | |||||
| self.setMenuMacrosEnabled(self.fPluginCount > 0) | |||||
| if pluginType == PLUGIN_LV2: | if pluginType == PLUGIN_LV2: | ||||
| self.fHasLoadedLv2Plugins = True | self.fHasLoadedLv2Plugins = True | ||||
| def setMenuMacrosEnabled(self, enabled): | |||||
| self.ui.act_plugin_remove_all.setEnabled(enabled) | |||||
| self.ui.act_plugins_enable.setEnabled(enabled) | |||||
| self.ui.act_plugins_disable.setEnabled(enabled) | |||||
| self.ui.act_plugins_volume100.setEnabled(enabled) | |||||
| self.ui.act_plugins_mute.setEnabled(enabled) | |||||
| self.ui.act_plugins_wet100.setEnabled(enabled) | |||||
| self.ui.act_plugins_bypass.setEnabled(enabled) | |||||
| self.ui.act_plugins_center.setEnabled(enabled) | |||||
| self.ui.act_plugins_change_skin.setEnabled(enabled) | |||||
| self.ui.act_plugins_compact.setEnabled(enabled) | |||||
| self.ui.act_plugins_expand.setEnabled(enabled) | |||||
| @pyqtSlot(int) | @pyqtSlot(int) | ||||
| def slot_handlePluginRemovedCallback(self, pluginId): | def slot_handlePluginRemovedCallback(self, pluginId): | ||||
| if self.fWithCanvas: | if self.fWithCanvas: | ||||
| @@ -1558,7 +1648,8 @@ class HostWindow(QMainWindow): | |||||
| del pitem | del pitem | ||||
| if self.fPluginCount == 0: | if self.fPluginCount == 0: | ||||
| self.ui.act_plugin_remove_all.setEnabled(False) | |||||
| # self.ui.act_plugin_remove_all.setEnabled(False) | |||||
| self.setMenuMacrosEnabled(False) | |||||
| if self.fCurrentlyRemovingAllPlugins: | if self.fCurrentlyRemovingAllPlugins: | ||||
| self.fCurrentlyRemovingAllPlugins = False | self.fCurrentlyRemovingAllPlugins = False | ||||
| self.projectLoadingFinished(False) | self.projectLoadingFinished(False) | ||||
| @@ -1569,7 +1660,9 @@ class HostWindow(QMainWindow): | |||||
| pitem = self.fPluginList[i] | pitem = self.fPluginList[i] | ||||
| pitem.setPluginId(i) | pitem.setPluginId(i) | ||||
| self.ui.act_plugin_remove_all.setEnabled(True) | |||||
| # self.ui.act_plugin_remove_all.setEnabled(True) | |||||
| self.setMenuMacrosEnabled(True) | |||||
| # -------------------------------------------------------------------------------------------------------- | # -------------------------------------------------------------------------------------------------------- | ||||
| # Canvas | # Canvas | ||||
| @@ -1975,6 +2068,7 @@ class HostWindow(QMainWindow): | |||||
| settings.setValue("Geometry", self.saveGeometry()) | settings.setValue("Geometry", self.saveGeometry()) | ||||
| settings.setValue("ShowToolbar", self.ui.toolBar.isEnabled()) | settings.setValue("ShowToolbar", self.ui.toolBar.isEnabled()) | ||||
| settings.setValue("ShowToolbarText", self.ui.act_settings_show_toolbar_text.isChecked()) | |||||
| settings.setValue("ShowSidePanel", self.ui.dockWidget.isEnabled()) | settings.setValue("ShowSidePanel", self.ui.dockWidget.isEnabled()) | ||||
| diskFolders = [] | diskFolders = [] | ||||
| @@ -2009,11 +2103,24 @@ class HostWindow(QMainWindow): | |||||
| showToolbar = settings.value("ShowToolbar", True, bool) | showToolbar = settings.value("ShowToolbar", True, bool) | ||||
| self.ui.act_settings_show_toolbar.setChecked(showToolbar) | self.ui.act_settings_show_toolbar.setChecked(showToolbar) | ||||
| self.ui.act_settings_show_toolbar_text.setEnabled(showToolbar) | |||||
| self.ui.toolBar.blockSignals(True) | self.ui.toolBar.blockSignals(True) | ||||
| self.ui.toolBar.setEnabled(showToolbar) | self.ui.toolBar.setEnabled(showToolbar) | ||||
| self.ui.toolBar.setVisible(showToolbar) | self.ui.toolBar.setVisible(showToolbar) | ||||
| self.ui.toolBar.blockSignals(False) | self.ui.toolBar.blockSignals(False) | ||||
| showToolbarText = settings.value("ShowToolbarText", True, bool) | |||||
| self.ui.act_settings_show_toolbar_text.setChecked(showToolbarText) | |||||
| self.ui.toolBar.blockSignals(True) | |||||
| for btn in self.ui.toolBar.findChildren(QToolButton): | |||||
| if showToolbarText: | |||||
| btn.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) | |||||
| else: | |||||
| btn.setToolButtonStyle(Qt.ToolButtonIconOnly) | |||||
| self.ui.toolBar.blockSignals(False) | |||||
| #if settings.contains("SplitterState"): | #if settings.contains("SplitterState"): | ||||
| #self.ui.splitter.restoreState(settings.value("SplitterState", b"")) | #self.ui.splitter.restoreState(settings.value("SplitterState", b"")) | ||||
| #else: | #else: | ||||
| @@ -2066,6 +2173,7 @@ class HostWindow(QMainWindow): | |||||
| CARLA_KEY_MAIN_REFRESH_INTERVAL: settings.value(CARLA_KEY_MAIN_REFRESH_INTERVAL, CARLA_DEFAULT_MAIN_REFRESH_INTERVAL, int), | CARLA_KEY_MAIN_REFRESH_INTERVAL: settings.value(CARLA_KEY_MAIN_REFRESH_INTERVAL, CARLA_DEFAULT_MAIN_REFRESH_INTERVAL, int), | ||||
| CARLA_KEY_MAIN_SYSTEM_ICONS: settings.value(CARLA_KEY_MAIN_SYSTEM_ICONS, CARLA_DEFAULT_MAIN_SYSTEM_ICONS, bool), | CARLA_KEY_MAIN_SYSTEM_ICONS: settings.value(CARLA_KEY_MAIN_SYSTEM_ICONS, CARLA_DEFAULT_MAIN_SYSTEM_ICONS, bool), | ||||
| CARLA_KEY_MAIN_EXPERIMENTAL: settings.value(CARLA_KEY_MAIN_EXPERIMENTAL, CARLA_DEFAULT_MAIN_EXPERIMENTAL, bool), | CARLA_KEY_MAIN_EXPERIMENTAL: settings.value(CARLA_KEY_MAIN_EXPERIMENTAL, CARLA_DEFAULT_MAIN_EXPERIMENTAL, bool), | ||||
| CARLA_KEY_MAIN_SKIN_TWEAKS: settings.value(CARLA_KEY_MAIN_SKIN_TWEAKS, CARLA_DEFAULT_MAIN_SKIN_TWEAKS, str), | |||||
| CARLA_KEY_CANVAS_THEME: settings.value(CARLA_KEY_CANVAS_THEME, CARLA_DEFAULT_CANVAS_THEME, str), | CARLA_KEY_CANVAS_THEME: settings.value(CARLA_KEY_CANVAS_THEME, CARLA_DEFAULT_CANVAS_THEME, str), | ||||
| CARLA_KEY_CANVAS_SIZE: settings.value(CARLA_KEY_CANVAS_SIZE, CARLA_DEFAULT_CANVAS_SIZE, str), | CARLA_KEY_CANVAS_SIZE: settings.value(CARLA_KEY_CANVAS_SIZE, CARLA_DEFAULT_CANVAS_SIZE, str), | ||||
| CARLA_KEY_CANVAS_AUTO_HIDE_GROUPS: settings.value(CARLA_KEY_CANVAS_AUTO_HIDE_GROUPS, CARLA_DEFAULT_CANVAS_AUTO_HIDE_GROUPS, bool), | CARLA_KEY_CANVAS_AUTO_HIDE_GROUPS: settings.value(CARLA_KEY_CANVAS_AUTO_HIDE_GROUPS, CARLA_DEFAULT_CANVAS_AUTO_HIDE_GROUPS, bool), | ||||
| @@ -2182,6 +2290,19 @@ class HostWindow(QMainWindow): | |||||
| self.ui.toolBar.blockSignals(True) | self.ui.toolBar.blockSignals(True) | ||||
| self.ui.toolBar.setEnabled(yesNo) | self.ui.toolBar.setEnabled(yesNo) | ||||
| self.ui.toolBar.setVisible(yesNo) | self.ui.toolBar.setVisible(yesNo) | ||||
| self.ui.act_settings_show_toolbar_text.setEnabled(yesNo) | |||||
| self.ui.toolBar.blockSignals(False) | |||||
| @pyqtSlot(bool) | |||||
| def slot_showToolbarText(self, yesNo): | |||||
| self.ui.toolBar.blockSignals(True) | |||||
| for btn in self.ui.toolBar.findChildren(QToolButton): | |||||
| if yesNo: | |||||
| btn.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) | |||||
| else: | |||||
| btn.setToolButtonStyle(Qt.ToolButtonIconOnly) | |||||
| self.ui.toolBar.blockSignals(False) | self.ui.toolBar.blockSignals(False) | ||||
| @pyqtSlot(bool) | @pyqtSlot(bool) | ||||
| @@ -2623,6 +2744,8 @@ class HostWindow(QMainWindow): | |||||
| self.ui.toolBar.setEnabled(visible) | self.ui.toolBar.setEnabled(visible) | ||||
| self.ui.toolBar.blockSignals(False) | self.ui.toolBar.blockSignals(False) | ||||
| self.ui.act_settings_show_toolbar.setChecked(visible) | self.ui.act_settings_show_toolbar.setChecked(visible) | ||||
| self.ui.act_settings_show_toolbar_text.setEnabled(visible) | |||||
| @pyqtSlot(int) | @pyqtSlot(int) | ||||
| def slot_tabChanged(self, index): | def slot_tabChanged(self, index): | ||||
| @@ -2889,7 +3012,6 @@ class HostWindow(QMainWindow): | |||||
| def idleFast(self): | def idleFast(self): | ||||
| self.host.engine_idle() | self.host.engine_idle() | ||||
| self.refreshTransport() | |||||
| if self.fPluginCount == 0 or self.fCurrentlyRemovingAllPlugins: | if self.fPluginCount == 0 or self.fCurrentlyRemovingAllPlugins: | ||||
| return | return | ||||
| @@ -2922,6 +3044,10 @@ class HostWindow(QMainWindow): | |||||
| def idleSlow(self): | def idleSlow(self): | ||||
| self.getAndRefreshRuntimeInfo() | self.getAndRefreshRuntimeInfo() | ||||
| self.slowTimer = (self.slowTimer + 1) % 4 | |||||
| if (self.slowTimer == 0): | |||||
| self.refreshTransport() # This one is CPU hungry. Ticket #1934 | |||||
| if self.fPluginCount == 0 or self.fCurrentlyRemovingAllPlugins: | if self.fPluginCount == 0 or self.fCurrentlyRemovingAllPlugins: | ||||
| return | return | ||||
| @@ -2949,6 +3075,13 @@ class HostWindow(QMainWindow): | |||||
| QMainWindow.changeEvent(self, event) | QMainWindow.changeEvent(self, event) | ||||
| def updateStyle(self): | def updateStyle(self): | ||||
| loadTweaks(self) | |||||
| if self.fTweaks.get('MoreSpace', 0): | |||||
| self.ui.pad_left.hide() | |||||
| self.ui.pad_right.hide() | |||||
| return | |||||
| # Rack padding images setup | # Rack padding images setup | ||||
| rack_imgL = QImage(":/bitmaps/rack_padding_left.png") | rack_imgL = QImage(":/bitmaps/rack_padding_left.png") | ||||
| rack_imgR = QImage(":/bitmaps/rack_padding_right.png") | rack_imgR = QImage(":/bitmaps/rack_padding_right.png") | ||||
| @@ -47,6 +47,7 @@ from carla_backend import ( | |||||
| from carla_shared import ( | from carla_shared import ( | ||||
| CARLA_KEY_MAIN_PROJECT_FOLDER, | CARLA_KEY_MAIN_PROJECT_FOLDER, | ||||
| CARLA_KEY_MAIN_USE_PRO_THEME, | CARLA_KEY_MAIN_USE_PRO_THEME, | ||||
| CARLA_KEY_MAIN_SKIN_TWEAKS, | |||||
| CARLA_KEY_MAIN_PRO_THEME_COLOR, | CARLA_KEY_MAIN_PRO_THEME_COLOR, | ||||
| CARLA_KEY_MAIN_REFRESH_INTERVAL, | CARLA_KEY_MAIN_REFRESH_INTERVAL, | ||||
| CARLA_KEY_MAIN_CONFIRM_EXIT, | CARLA_KEY_MAIN_CONFIRM_EXIT, | ||||
| @@ -115,6 +116,7 @@ from carla_shared import ( | |||||
| CARLA_DEFAULT_MAIN_CLASSIC_SKIN, | CARLA_DEFAULT_MAIN_CLASSIC_SKIN, | ||||
| CARLA_DEFAULT_MAIN_SHOW_LOGS, | CARLA_DEFAULT_MAIN_SHOW_LOGS, | ||||
| CARLA_DEFAULT_MAIN_SYSTEM_ICONS, | CARLA_DEFAULT_MAIN_SYSTEM_ICONS, | ||||
| CARLA_DEFAULT_MAIN_SKIN_TWEAKS, | |||||
| #CARLA_DEFAULT_MAIN_EXPERIMENTAL, | #CARLA_DEFAULT_MAIN_EXPERIMENTAL, | ||||
| CARLA_DEFAULT_CANVAS_THEME, | CARLA_DEFAULT_CANVAS_THEME, | ||||
| CARLA_DEFAULT_CANVAS_SIZE, | CARLA_DEFAULT_CANVAS_SIZE, | ||||
| @@ -705,6 +707,9 @@ class CarlaSettingsW(QDialog): | |||||
| self.ui.cb_main_theme_color.findText(settings.value(CARLA_KEY_MAIN_PRO_THEME_COLOR, | self.ui.cb_main_theme_color.findText(settings.value(CARLA_KEY_MAIN_PRO_THEME_COLOR, | ||||
| CARLA_DEFAULT_MAIN_PRO_THEME_COLOR, str))) | CARLA_DEFAULT_MAIN_PRO_THEME_COLOR, str))) | ||||
| self.ui.le_main_skin_tweaks.setText( | |||||
| settings.value(CARLA_KEY_MAIN_SKIN_TWEAKS, CARLA_DEFAULT_MAIN_SKIN_TWEAKS, str)) | |||||
| self.ui.sb_main_refresh_interval.setValue( | self.ui.sb_main_refresh_interval.setValue( | ||||
| settings.value(CARLA_KEY_MAIN_REFRESH_INTERVAL, CARLA_DEFAULT_MAIN_REFRESH_INTERVAL, int)) | settings.value(CARLA_KEY_MAIN_REFRESH_INTERVAL, CARLA_DEFAULT_MAIN_REFRESH_INTERVAL, int)) | ||||
| @@ -995,6 +1000,7 @@ class CarlaSettingsW(QDialog): | |||||
| settings.setValue(CARLA_KEY_MAIN_CONFIRM_EXIT, self.ui.ch_main_confirm_exit.isChecked()) | settings.setValue(CARLA_KEY_MAIN_CONFIRM_EXIT, self.ui.ch_main_confirm_exit.isChecked()) | ||||
| settings.setValue(CARLA_KEY_MAIN_CLASSIC_SKIN, self.ui.cb_main_classic_skin_default.isChecked()) | settings.setValue(CARLA_KEY_MAIN_CLASSIC_SKIN, self.ui.cb_main_classic_skin_default.isChecked()) | ||||
| settings.setValue(CARLA_KEY_MAIN_USE_PRO_THEME, self.ui.ch_main_theme_pro.isChecked()) | settings.setValue(CARLA_KEY_MAIN_USE_PRO_THEME, self.ui.ch_main_theme_pro.isChecked()) | ||||
| settings.setValue(CARLA_KEY_MAIN_SKIN_TWEAKS, self.ui.le_main_skin_tweaks.text()) | |||||
| settings.setValue(CARLA_KEY_MAIN_PRO_THEME_COLOR, self.ui.cb_main_theme_color.currentText()) | settings.setValue(CARLA_KEY_MAIN_PRO_THEME_COLOR, self.ui.cb_main_theme_color.currentText()) | ||||
| settings.setValue(CARLA_KEY_MAIN_REFRESH_INTERVAL, self.ui.sb_main_refresh_interval.value()) | settings.setValue(CARLA_KEY_MAIN_REFRESH_INTERVAL, self.ui.sb_main_refresh_interval.value()) | ||||
| settings.setValue(CARLA_KEY_MAIN_SYSTEM_ICONS, self.ui.ch_main_system_icons.isChecked()) | settings.setValue(CARLA_KEY_MAIN_SYSTEM_ICONS, self.ui.ch_main_system_icons.isChecked()) | ||||
| @@ -1193,6 +1199,7 @@ class CarlaSettingsW(QDialog): | |||||
| self.ui.group_main_theme.isEnabled()) | self.ui.group_main_theme.isEnabled()) | ||||
| self.ui.cb_main_theme_color.setCurrentIndex( | self.ui.cb_main_theme_color.setCurrentIndex( | ||||
| self.ui.cb_main_theme_color.findText(CARLA_DEFAULT_MAIN_PRO_THEME_COLOR)) | self.ui.cb_main_theme_color.findText(CARLA_DEFAULT_MAIN_PRO_THEME_COLOR)) | ||||
| self.ui.le_main_skin_tweaks.setText(CARLA_DEFAULT_MAIN_SKIN_TWEAKS) | |||||
| self.ui.sb_main_refresh_interval.setValue(CARLA_DEFAULT_MAIN_REFRESH_INTERVAL) | self.ui.sb_main_refresh_interval.setValue(CARLA_DEFAULT_MAIN_REFRESH_INTERVAL) | ||||
| self.ui.ch_main_confirm_exit.setChecked(CARLA_DEFAULT_MAIN_CONFIRM_EXIT) | self.ui.ch_main_confirm_exit.setChecked(CARLA_DEFAULT_MAIN_CONFIRM_EXIT) | ||||
| self.ui.cb_main_classic_skin_default(CARLA_DEFAULT_MAIN_CLASSIC_SKIN) | self.ui.cb_main_classic_skin_default(CARLA_DEFAULT_MAIN_CLASSIC_SKIN) | ||||
| @@ -8,7 +8,7 @@ | |||||
| import os | import os | ||||
| import sys | import sys | ||||
| from math import fmod | |||||
| from math import fmod, log10 | |||||
| # ------------------------------------------------------------------------------------------------------------ | # ------------------------------------------------------------------------------------------------------------ | ||||
| # Imports (Signal) | # Imports (Signal) | ||||
| @@ -63,6 +63,9 @@ from carla_backend import ( | |||||
| ENGINE_TRANSPORT_MODE_JACK, | ENGINE_TRANSPORT_MODE_JACK, | ||||
| ) | ) | ||||
| from utils import QSafeSettings | |||||
| import ast | |||||
| # ------------------------------------------------------------------------------------------------------------ | # ------------------------------------------------------------------------------------------------------------ | ||||
| # Config | # Config | ||||
| @@ -184,6 +187,7 @@ CANVAS_EYECANDY_SMALL = 1 | |||||
| CARLA_KEY_MAIN_PROJECT_FOLDER = "Main/ProjectFolder" # str | CARLA_KEY_MAIN_PROJECT_FOLDER = "Main/ProjectFolder" # str | ||||
| CARLA_KEY_MAIN_USE_PRO_THEME = "Main/UseProTheme" # bool | CARLA_KEY_MAIN_USE_PRO_THEME = "Main/UseProTheme" # bool | ||||
| CARLA_KEY_MAIN_PRO_THEME_COLOR = "Main/ProThemeColor" # str | CARLA_KEY_MAIN_PRO_THEME_COLOR = "Main/ProThemeColor" # str | ||||
| CARLA_KEY_MAIN_SKIN_TWEAKS = "Main/SkinTweaks" # str | |||||
| CARLA_KEY_MAIN_REFRESH_INTERVAL = "Main/RefreshInterval" # int | CARLA_KEY_MAIN_REFRESH_INTERVAL = "Main/RefreshInterval" # int | ||||
| CARLA_KEY_MAIN_CONFIRM_EXIT = "Main/ConfirmExit" # bool | CARLA_KEY_MAIN_CONFIRM_EXIT = "Main/ConfirmExit" # bool | ||||
| CARLA_KEY_MAIN_CLASSIC_SKIN = "Main/ClassicSkin" # bool | CARLA_KEY_MAIN_CLASSIC_SKIN = "Main/ClassicSkin" # bool | ||||
| @@ -273,6 +277,7 @@ CARLA_DEFAULT_MAIN_CLASSIC_SKIN = False | |||||
| CARLA_DEFAULT_MAIN_SHOW_LOGS = bool(not CARLA_OS_WIN) | CARLA_DEFAULT_MAIN_SHOW_LOGS = bool(not CARLA_OS_WIN) | ||||
| CARLA_DEFAULT_MAIN_SYSTEM_ICONS = False | CARLA_DEFAULT_MAIN_SYSTEM_ICONS = False | ||||
| CARLA_DEFAULT_MAIN_EXPERIMENTAL = False | CARLA_DEFAULT_MAIN_EXPERIMENTAL = False | ||||
| CARLA_DEFAULT_MAIN_SKIN_TWEAKS = "'ShowPan':0, 'ShowForth':0, 'WetVolPush':0, 'WetVolPushLed':1, 'Tooltips':0, 'MoreSpace':0, 'WetVolOnCompact':0, 'SymmetricArc':1, 'GapAuto':0, 'ColorFollow':0, 'ShortenLabels':1, 'ButtonHaveLed':1, 'ColoredNeon':1, 'HighContrast':0, 'ShowDisabled':0, 'ShowOutputs':1, 'ShowButtons':1, 'Button3Pos':1, 'TwoLineLabels':0, 'GapMin':0, 'GapMax':100, 'ColorFrom':-0.1, 'ColorSpan':0.4, 'Auto7segSize':0, 'Auto7segWidth':1, 'ShowReload':0, 'ShowPrograms':0, 'ShowMidiPrograms':0, 'ShowProgramsOnCompact':0, 'ShowMidiProgramsOnCompact':0, " | |||||
| # Canvas | # Canvas | ||||
| CARLA_DEFAULT_CANVAS_THEME = "Modern Dark" | CARLA_DEFAULT_CANVAS_THEME = "Modern Dark" | ||||
| @@ -647,18 +652,22 @@ else: | |||||
| # Find decimal points for a parameter, using step and stepSmall | # Find decimal points for a parameter, using step and stepSmall | ||||
| def countDecimalPoints(step, stepSmall): | def countDecimalPoints(step, stepSmall): | ||||
| if stepSmall >= 1.0: | |||||
| if (stepSmall >= 1.0) or (step <= 0) or (stepSmall <= 0): | |||||
| return 0 | return 0 | ||||
| if step >= 1.0: | if step >= 1.0: | ||||
| return 2 | return 2 | ||||
| count = 0 | |||||
| value = fmod(abs(step), 1) | |||||
| while 0.0001 < value < 0.999 and count < 6: | |||||
| value = fmod(value*10, 1) | |||||
| count += 1 | |||||
| # OLD | |||||
| # count = 0 | |||||
| # value = fmod(abs(step), 1) | |||||
| # while 0.0001 < value < 0.999 and count < 6: | |||||
| # value = fmod(value*10, 1) | |||||
| # count += 1 | |||||
| # | |||||
| # return count | |||||
| return count | |||||
| # NEW: Looks like better handling of small values. | |||||
| return -int(log10(stepSmall)) + 2 | |||||
| # ------------------------------------------------------------------------------------------------------------ | # ------------------------------------------------------------------------------------------------------------ | ||||
| # Check if a value is a number (float support) | # Check if a value is a number (float support) | ||||
| @@ -927,5 +936,50 @@ def CustomMessageBox(parent, icon, title, text, | |||||
| # pylint: enable=no-value-for-parameter | # pylint: enable=no-value-for-parameter | ||||
| # pylint: enable=too-many-arguments | # pylint: enable=too-many-arguments | ||||
| # ------------------------------------------------------------------------------------------------------------ | |||||
| # Tweaks, in form of 'Parameter':Value or 'skinnameParameter':Value, are holds both per-rack and per-plugin fine-tuning values (tweaks). | |||||
| def loadTweaks(self): | |||||
| settings = QSafeSettings("falkTX", "Carla2") | |||||
| skinTweaks = settings.value(CARLA_KEY_MAIN_SKIN_TWEAKS, CARLA_DEFAULT_MAIN_SKIN_TWEAKS, str) | |||||
| try: | |||||
| self.fTweaks = ast.literal_eval('{' + skinTweaks + '}') | |||||
| except ValueError as e: | |||||
| print("ERROR while parse `" + skinTweaks + "` :", e) | |||||
| # ------------------------------------------------------------------------------------------------------------ | |||||
| def getPrefixSuffix(unit): | |||||
| prefix = "" | |||||
| suffix = unit.strip() | |||||
| if suffix == "(coef)": | |||||
| prefix = "* " | |||||
| suffix = "" | |||||
| else: | |||||
| suffix = " " + suffix | |||||
| return prefix, suffix | |||||
| # ------------------------------------------------------------------------------------------------------------ | |||||
| def strLim(value, digits = 5): | |||||
| # np.format_float_positional(value, trim='-', fractional=False, precision=digits) | |||||
| result = "%.5f" % value | |||||
| if "." in result: | |||||
| result = result.strip("0") | |||||
| if result[-1] == ".": | |||||
| result = result.removesuffix(".") | |||||
| if len(result) > 9: | |||||
| return '{:.3e}'.format(value) | |||||
| else: | |||||
| return result | |||||
| # ------------------------------------------------------------------------------------------------------------ | |||||
| # Geometry | |||||
| RACK_KNOB_GAP = 5 | |||||
| # ------------------------------------------------------------------------------------------------------------ | # ------------------------------------------------------------------------------------------------------------ | ||||
| # pylint: enable=possibly-used-before-assignment | # pylint: enable=possibly-used-before-assignment | ||||
| @@ -6,15 +6,19 @@ | |||||
| # Imports (Global) | # Imports (Global) | ||||
| from qt_compat import qt_config | from qt_compat import qt_config | ||||
| import math | |||||
| import random | |||||
| import operator | |||||
| from operator import itemgetter | |||||
| if qt_config == 5: | if qt_config == 5: | ||||
| from PyQt5.QtCore import Qt, QRectF, QLineF, QTimer | from PyQt5.QtCore import Qt, QRectF, QLineF, QTimer | ||||
| from PyQt5.QtGui import QColor, QFont, QFontDatabase, QPainter, QPainterPath, QPen | from PyQt5.QtGui import QColor, QFont, QFontDatabase, QPainter, QPainterPath, QPen | ||||
| from PyQt5.QtWidgets import QColorDialog, QFrame, QLineEdit, QPushButton | |||||
| from PyQt5.QtWidgets import QColorDialog, QFrame, QLineEdit, QPushButton, QComboBox, QSizePolicy | |||||
| elif qt_config == 6: | elif qt_config == 6: | ||||
| from PyQt6.QtCore import Qt, QRectF, QLineF, QTimer | from PyQt6.QtCore import Qt, QRectF, QLineF, QTimer | ||||
| from PyQt6.QtGui import QColor, QFont, QFontDatabase, QPainter, QPainterPath, QPen | from PyQt6.QtGui import QColor, QFont, QFontDatabase, QPainter, QPainterPath, QPen | ||||
| from PyQt6.QtWidgets import QColorDialog, QFrame, QLineEdit, QPushButton | |||||
| from PyQt6.QtWidgets import QColorDialog, QFrame, QLineEdit, QPushButton, QComboBox, QSizePolicy | |||||
| # ------------------------------------------------------------------------------------------------------------ | # ------------------------------------------------------------------------------------------------------------ | ||||
| # Imports (Custom) | # Imports (Custom) | ||||
| @@ -29,7 +33,6 @@ from carla_backend import * | |||||
| from carla_shared import * | from carla_shared import * | ||||
| from carla_widgets import * | from carla_widgets import * | ||||
| from widgets.digitalpeakmeter import DigitalPeakMeter | from widgets.digitalpeakmeter import DigitalPeakMeter | ||||
| from widgets.paramspinbox import CustomInputDialog | |||||
| from widgets.scalabledial import ScalableDial | from widgets.scalabledial import ScalableDial | ||||
| # ------------------------------------------------------------------------------------------------------------ | # ------------------------------------------------------------------------------------------------------------ | ||||
| @@ -126,7 +129,7 @@ def getParameterShortName(paramName): | |||||
| # Get RGB colors for a plugin category | # Get RGB colors for a plugin category | ||||
| def getColorFromCategory(category): | def getColorFromCategory(category): | ||||
| r = 40 | |||||
| r = 39 | |||||
| g = 40 | g = 40 | ||||
| b = 40 | b = 40 | ||||
| @@ -152,45 +155,35 @@ def getColorFromCategory(category): | |||||
| return (r, g, b) | return (r, g, b) | ||||
| # ------------------------------------------------------------------------------------------------------------ | # ------------------------------------------------------------------------------------------------------------ | ||||
| # | |||||
| def setScalableDialStyle(widget, parameterId, parameterCount, whiteLabels, skinStyle): | |||||
| if skinStyle.startswith("calf"): | |||||
| widget.setCustomPaintMode(ScalableDial.CUSTOM_PAINT_MODE_NO_GRADIENT) | |||||
| widget.setImage(7) | |||||
| elif skinStyle.startswith("openav"): | |||||
| widget.setCustomPaintMode(ScalableDial.CUSTOM_PAINT_MODE_NO_GRADIENT) | |||||
| if parameterId == PARAMETER_DRYWET: | |||||
| widget.setImage(13) | |||||
| elif parameterId == PARAMETER_VOLUME: | |||||
| widget.setImage(12) | |||||
| else: | |||||
| widget.setImage(11) | |||||
| else: | |||||
| if parameterId == PARAMETER_DRYWET: | |||||
| widget.setCustomPaintMode(ScalableDial.CUSTOM_PAINT_MODE_CARLA_WET) | |||||
| elif parameterId == PARAMETER_VOLUME: | |||||
| widget.setCustomPaintMode(ScalableDial.CUSTOM_PAINT_MODE_CARLA_VOL) | |||||
| else: | |||||
| _r = 255 - int((float(parameterId)/float(parameterCount))*200.0) | |||||
| _g = 55 + int((float(parameterId)/float(parameterCount))*200.0) | |||||
| _b = 0 #(r-40)*4 | |||||
| widget.setCustomPaintColor(QColor(_r, _g, _b)) | |||||
| widget.setCustomPaintMode(ScalableDial.CUSTOM_PAINT_MODE_COLOR) | |||||
| if whiteLabels: | |||||
| colorEnabled = QColor("#BBB") | |||||
| colorDisabled = QColor("#555") | |||||
| else: | |||||
| colorEnabled = QColor("#111") | |||||
| colorDisabled = QColor("#AAA") | |||||
| widget.setLabelColor(colorEnabled, colorDisabled) | |||||
| widget.setImage(3) | |||||
| skinList = [ | |||||
| "default", | |||||
| "3bandeq", | |||||
| "rncbc", | |||||
| "calf_black", | |||||
| "calf_blue", | |||||
| "classic", | |||||
| "openav-old", | |||||
| "openav", | |||||
| "zynfx", | |||||
| "presets", | |||||
| "mpresets", | |||||
| "tube", | |||||
| ] | |||||
| skinListTweakable = [ | |||||
| "default", | |||||
| "calf", | |||||
| "openav", | |||||
| "zynfx", | |||||
| "tube", | |||||
| ] | |||||
| def arrayIndex(array, value): | |||||
| for index, item in enumerate(array): | |||||
| if item.startswith(value): | |||||
| return index | |||||
| return 0 | |||||
| # ------------------------------------------------------------------------------------------------------------ | # ------------------------------------------------------------------------------------------------------------ | ||||
| # Abstract plugin slot | # Abstract plugin slot | ||||
| @@ -235,6 +228,16 @@ class AbstractPluginSlot(QFrame, PluginEditParentMeta): | |||||
| self.fAdjustViewableKnobCountScheduled = False | self.fAdjustViewableKnobCountScheduled = False | ||||
| # load fresh skin tweaks | |||||
| self.fTweaks = {} | |||||
| loadTweaks(self) | |||||
| # take panel color hue & sat to make "follow panel" paint | |||||
| color = QColor(skinColor[0], skinColor[1], skinColor[2]) | |||||
| hue = color.hueF() % 1.0 | |||||
| sat = color.saturationF() | |||||
| self.fColorHint = int(hue * 100) + int(sat * 100) / 100.0 # 50.80: 50% hue, 80% sat | |||||
| # used during testing | # used during testing | ||||
| self.fIdleTimerId = 0 | self.fIdleTimerId = 0 | ||||
| @@ -269,6 +272,8 @@ class AbstractPluginSlot(QFrame, PluginEditParentMeta): | |||||
| self.w_knobs_right = None | self.w_knobs_right = None | ||||
| self.spacer_knobs = None | self.spacer_knobs = None | ||||
| self.slowTimer = 0 | |||||
| # ------------------------------------------------------------- | # ------------------------------------------------------------- | ||||
| # Set-up connections | # Set-up connections | ||||
| @@ -348,8 +353,31 @@ class AbstractPluginSlot(QFrame, PluginEditParentMeta): | |||||
| if self.fEditDialog is not None and self.fPluginId == pluginId: | if self.fEditDialog is not None and self.fPluginId == pluginId: | ||||
| self.customUiStateChanged(state) | self.customUiStateChanged(state) | ||||
| # @pyqtSlot(int, int, int) | |||||
| # def slot_handleParameterKnobVisible(self, pluginId, index, value): | |||||
| # if self.fEditDialog is not None and self.fPluginId == pluginId: | |||||
| # self.setKnobVisible(index, value) | |||||
| # @pyqtSlot(bool) | |||||
| # def slot_knobVisible(self, value): | |||||
| # self.host.set_drywet(self.fPluginId, value) | |||||
| # self.setParameterValue(PARAMETER_DRYWET, value, True) | |||||
| @pyqtSlot(float) | |||||
| def slot_dryWetChanged(self, value): | |||||
| self.host.set_drywet(self.fPluginId, value) | |||||
| self.setParameterValue(PARAMETER_DRYWET, value, True) | |||||
| @pyqtSlot(float) | |||||
| def slot_volumeChanged(self, value): | |||||
| self.host.set_volume(self.fPluginId, value) | |||||
| self.setParameterValue(PARAMETER_VOLUME, value, True) | |||||
| # ------------------------------------------------------------------ | # ------------------------------------------------------------------ | ||||
| def tweak(self, skinName, tweakName, default): | |||||
| return self.fTweaks.get(skinName + tweakName, self.fTweaks.get(tweakName, default)) | |||||
| def ready(self): | def ready(self): | ||||
| self.fIsActive = bool(self.host.get_internal_parameter_value(self.fPluginId, PARAMETER_ACTIVE) >= 0.5) | self.fIsActive = bool(self.host.get_internal_parameter_value(self.fPluginId, PARAMETER_ACTIVE) >= 0.5) | ||||
| @@ -481,6 +509,9 @@ class AbstractPluginSlot(QFrame, PluginEditParentMeta): | |||||
| self.peak_in.setMeterStyle(DigitalPeakMeter.STYLE_RNCBC) | self.peak_in.setMeterStyle(DigitalPeakMeter.STYLE_RNCBC) | ||||
| elif self.fSkinStyle.startswith("openav") or self.fSkinStyle == "zynfx": | elif self.fSkinStyle.startswith("openav") or self.fSkinStyle == "zynfx": | ||||
| self.peak_in.setMeterStyle(DigitalPeakMeter.STYLE_OPENAV) | self.peak_in.setMeterStyle(DigitalPeakMeter.STYLE_OPENAV) | ||||
| elif self.fSkinStyle == "tube": | |||||
| self.peak_in.setMeterStyle(DigitalPeakMeter.STYLE_TUBE) | |||||
| self.peak_in.setMeterLinesEnabled(False) | |||||
| if self.fPeaksInputCount == 0 and not isinstance(self, PluginSlot_Classic): | if self.fPeaksInputCount == 0 and not isinstance(self, PluginSlot_Classic): | ||||
| self.peak_in.hide() | self.peak_in.hide() | ||||
| @@ -496,6 +527,9 @@ class AbstractPluginSlot(QFrame, PluginEditParentMeta): | |||||
| self.peak_out.setMeterStyle(DigitalPeakMeter.STYLE_RNCBC) | self.peak_out.setMeterStyle(DigitalPeakMeter.STYLE_RNCBC) | ||||
| elif self.fSkinStyle.startswith("openav") or self.fSkinStyle == "zynfx": | elif self.fSkinStyle.startswith("openav") or self.fSkinStyle == "zynfx": | ||||
| self.peak_out.setMeterStyle(DigitalPeakMeter.STYLE_OPENAV) | self.peak_out.setMeterStyle(DigitalPeakMeter.STYLE_OPENAV) | ||||
| elif self.fSkinStyle == "tube": | |||||
| self.peak_out.setMeterStyle(DigitalPeakMeter.STYLE_TUBE) | |||||
| self.peak_out.setMeterLinesEnabled(False) | |||||
| if self.fPeaksOutputCount == 0 and not isinstance(self, PluginSlot_Classic): | if self.fPeaksOutputCount == 0 and not isinstance(self, PluginSlot_Classic): | ||||
| self.peak_out.hide() | self.peak_out.hide() | ||||
| @@ -530,7 +564,8 @@ class AbstractPluginSlot(QFrame, PluginEditParentMeta): | |||||
| styleSheet2 = "background-image: url(:/bitmaps/background_%s.png);" % self.fSkinStyle | styleSheet2 = "background-image: url(:/bitmaps/background_%s.png);" % self.fSkinStyle | ||||
| else: | else: | ||||
| styleSheet2 = "background-color: rgb(200, 200, 200);" | styleSheet2 = "background-color: rgb(200, 200, 200);" | ||||
| styleSheet2 += "background-image: url(:/bitmaps/background_noise1.png);" | |||||
| if self.fSkinStyle not in ("classic"): | |||||
| styleSheet2 += "background-image: url(:/bitmaps/background_noise1.png);" | |||||
| if not self.fDarkStyle: | if not self.fDarkStyle: | ||||
| colorEnabled = "#111" | colorEnabled = "#111" | ||||
| @@ -551,12 +586,63 @@ class AbstractPluginSlot(QFrame, PluginEditParentMeta): | |||||
| styleSheet += """ | styleSheet += """ | ||||
| QComboBox#cb_presets, | QComboBox#cb_presets, | ||||
| QComboBox#cb_presets0, | |||||
| QComboBox#cb_presets1, | |||||
| QLabel#label_audio_in, | QLabel#label_audio_in, | ||||
| QLabel#label_audio_out, | QLabel#label_audio_out, | ||||
| QLabel#label_midi { font-size: 10px; } | QLabel#label_midi { font-size: 10px; } | ||||
| """ | """ | ||||
| self.setStyleSheet(styleSheet) | self.setStyleSheet(styleSheet) | ||||
| # ------------------------------------------------------------- | |||||
| # Wet and Vol knobs on compacted slot | |||||
| # If "long" style not in "shorts" list, it will be matched to 0 ("default"). | |||||
| skinNum = arrayIndex(skinListTweakable, self.fSkinStyle[: 3]) | |||||
| skinName = skinListTweakable [skinNum] | |||||
| wetVolOnCompact = self.tweak(skinName, 'WetVolOnCompact', 0) | |||||
| showDisabled = self.tweak(skinName, 'ShowDisabled', 0) | |||||
| showOutputs = self.tweak(skinName, 'ShowOutputs', 0) | |||||
| shortenLabels = self.tweak(skinName, 'ShortenLabels', 1) | |||||
| btn3state = self.tweak(skinName, 'Button3Pos', 1) | |||||
| if isinstance(self, PluginSlot_Compact): | |||||
| if self.fPluginInfo['hints'] & PLUGIN_CAN_DRYWET or showDisabled: | |||||
| self.dial0 = ScalableDial(self, PARAMETER_DRYWET, 100, 1.0, 0.0, 1.0, "Dry/Wet", ScalableDial.CUSTOM_PAINT_MODE_CARLA_WET_MINI, -1, "%", self.fSkinStyle, whiteLabels, self.fTweaks) | |||||
| self.dial0.setObjectName("dial0") | |||||
| self.ui.horizontalLayout_2.insertWidget(6, self.dial0) | |||||
| if wetVolOnCompact: | |||||
| self.dial0.setEnabled(bool(self.fPluginInfo['hints'] & PLUGIN_CAN_DRYWET)) | |||||
| self.dial0.dragStateChanged.connect(self.slot_parameterDragStateChanged) | |||||
| self.dial0.realValueChanged.connect(self.slot_dryWetChanged) | |||||
| self.dial0.customContextMenuRequested.connect(self.slot_knobCustomMenu) | |||||
| self.dial0.blockSignals(True) | |||||
| self.dial0.setValue(self.host.get_internal_parameter_value(self.fPluginId, PARAMETER_DRYWET)) | |||||
| self.dial0.blockSignals(False) | |||||
| else: | |||||
| self.dial0.hide() | |||||
| if self.fPluginInfo['hints'] & PLUGIN_CAN_VOLUME or showDisabled: | |||||
| # self.dial1 = ScalableDial(self.ui.dial1, PARAMETER_VOLUME, 254, 1.0, 0.0, 1.27, "Volume", ScalableDial.CUSTOM_PAINT_MODE_CARLA_VOL_MINI, 0, "%", self.fSkinStyle, whiteLabels, self.fTweaks) | |||||
| self.dial1 = ScalableDial(self, PARAMETER_VOLUME, 127, 1.0, 0.0, 1.27, "Volume", ScalableDial.CUSTOM_PAINT_MODE_CARLA_VOL_MINI, -1, "%", self.fSkinStyle, whiteLabels, self.fTweaks) | |||||
| self.dial1.setObjectName("dial1") | |||||
| self.ui.horizontalLayout_2.insertWidget(6, self.dial1) | |||||
| if wetVolOnCompact: | |||||
| self.dial1.setEnabled(bool(self.fPluginInfo['hints'] & PLUGIN_CAN_VOLUME)) | |||||
| self.dial1.dragStateChanged.connect(self.slot_parameterDragStateChanged) | |||||
| self.dial1.realValueChanged.connect(self.slot_volumeChanged) | |||||
| self.dial1.customContextMenuRequested.connect(self.slot_knobCustomMenu) | |||||
| self.dial1.blockSignals(True) | |||||
| self.dial1.setValue(self.host.get_internal_parameter_value(self.fPluginId, PARAMETER_VOLUME)) | |||||
| self.dial1.blockSignals(False) | |||||
| else: | |||||
| self.dial1.hide() | |||||
| # ------------------------------------------------------------- | # ------------------------------------------------------------- | ||||
| # Set-up parameters | # Set-up parameters | ||||
| @@ -565,6 +651,11 @@ class AbstractPluginSlot(QFrame, PluginEditParentMeta): | |||||
| index = 0 | index = 0 | ||||
| layout = self.w_knobs_left.layout() | layout = self.w_knobs_left.layout() | ||||
| # Rainbow paint, default is deep red -> green. Span can be negative. | |||||
| hueFrom = self.tweak(skinName, 'ColorFrom', -0.03) | |||||
| hueSpan = self.tweak(skinName, 'ColorSpan', 0.4) | |||||
| for i in range(parameterCount): | for i in range(parameterCount): | ||||
| # 50 should be enough for everybody, right? | # 50 should be enough for everybody, right? | ||||
| if index >= 50: | if index >= 50: | ||||
| @@ -573,64 +664,145 @@ class AbstractPluginSlot(QFrame, PluginEditParentMeta): | |||||
| paramInfo = self.host.get_parameter_info(self.fPluginId, i) | paramInfo = self.host.get_parameter_info(self.fPluginId, i) | ||||
| paramData = self.host.get_parameter_data(self.fPluginId, i) | paramData = self.host.get_parameter_data(self.fPluginId, i) | ||||
| paramRanges = self.host.get_parameter_ranges(self.fPluginId, i) | paramRanges = self.host.get_parameter_ranges(self.fPluginId, i) | ||||
| isInteger = (paramData['hints'] & PARAMETER_IS_INTEGER) != 0 | |||||
| default = self.host.get_default_parameter_value(self.fPluginId, i) | |||||
| minimum = paramRanges['min'] | |||||
| maximum = paramRanges['max'] | |||||
| isEnabled = (paramData['hints'] & PARAMETER_IS_ENABLED) != 0 | |||||
| isOutput = (paramData['type'] != PARAMETER_INPUT) | |||||
| isBoolean = (paramData['hints'] & PARAMETER_IS_BOOLEAN) != 0 | |||||
| isInteger = ((paramData['hints'] & PARAMETER_IS_INTEGER) != 0) or isBoolean | |||||
| if paramData['type'] != PARAMETER_INPUT: | |||||
| continue | |||||
| if paramData['hints'] & PARAMETER_IS_BOOLEAN: | |||||
| continue | |||||
| if (paramData['hints'] & PARAMETER_IS_ENABLED) == 0: | |||||
| continue | |||||
| if (paramData['hints'] & PARAMETER_USES_SCALEPOINTS) != 0 and not isInteger: | |||||
| # NOTE: we assume integer scalepoints are continuous | |||||
| continue | |||||
| if isInteger and paramRanges['max']-paramRanges['min'] <= 3: | |||||
| continue | |||||
| if paramInfo['name'].startswith("unused"): | if paramInfo['name'].startswith("unused"): | ||||
| print("Carla: INFO: Parameter "+str(i)+" is Unused, so skipped.") | |||||
| continue | continue | ||||
| paramName = getParameterShortName(paramInfo['name']) | |||||
| if not isEnabled: | |||||
| print("Carla: INFO: Parameter "+str(i)+" is Disabled.") | |||||
| if not showDisabled: | |||||
| continue | |||||
| delta = maximum - minimum | |||||
| if delta <= 0: | |||||
| print("Carla: ERROR: Parameter "+str(i)+": Min, Max are same or wrong.") | |||||
| return | |||||
| # NOTE: Booleans are mimic as isInteger with range [0 or 1]. | |||||
| if btn3state: | |||||
| isButton = (isInteger and (minimum == 0) and (maximum in (1, 2))) | |||||
| else: | |||||
| isButton = (isInteger and (minimum == 0) and (maximum == 1)) | |||||
| vuMeter = 0 | |||||
| precision = 1 | |||||
| if isOutput: | |||||
| if not showOutputs: | |||||
| continue | |||||
| vuMeter = ((minimum == 0) and ((maximum == 1) or (maximum == 100)))\ | |||||
| or (minimum == -maximum) # from -N to N, is it good to use VU ? | |||||
| else: | |||||
| # Integers have somewhat more coarse step | |||||
| if isInteger: | |||||
| while delta > 50: | |||||
| delta = int(math.ceil(delta / 2)) | |||||
| precision = delta | |||||
| # Floats are finer-step smoothed | |||||
| else: | |||||
| # Pretty steps for most common values, like 1-2-5-10 scales, | |||||
| # still not in its final form. | |||||
| while delta > 200: | |||||
| # Mantissa is near 2.5 | |||||
| is25 = int(abs((log10(delta) % 1) - log10(2.5)) < 0.001) | |||||
| delta = delta / (2.0 + is25 * 0.5) | |||||
| while delta < 100: | |||||
| # Mantissa is near 2.0 | |||||
| is25 = int(abs((log10(delta) % 1) - log10(2.0)) < 0.001) | |||||
| delta = delta * (2.0 + is25 * 0.5) | |||||
| precision = math.ceil(delta) | |||||
| if precision <= 0: # suddenly... | |||||
| print("Carla: ERROR: Parameter "+str(i)+": Precision "+str(precision)+" is wrong!") | |||||
| return | |||||
| if shortenLabels: | |||||
| label = getParameterShortName(paramInfo['name']) | |||||
| else: | |||||
| label = paramInfo['name'] | |||||
| widget = ScalableDial(self, i, | |||||
| precision, | |||||
| default, | |||||
| minimum, | |||||
| maximum, | |||||
| label, | |||||
| skinNum * 16, | |||||
| self.fColorHint, | |||||
| paramInfo['unit'], | |||||
| self.fSkinStyle, | |||||
| whiteLabels, | |||||
| self.fTweaks, | |||||
| isInteger, | |||||
| isButton, | |||||
| isOutput, | |||||
| vuMeter, | |||||
| 1 ) # isVisible Experiment (index % 2) | |||||
| widget.setEnabled(isEnabled) | |||||
| widget = ScalableDial(self, i) | |||||
| widget.setLabel(paramName) | |||||
| widget.setMinimum(paramRanges['min']) | |||||
| widget.setMaximum(paramRanges['max']) | |||||
| widget.hide() | widget.hide() | ||||
| if isInteger: | |||||
| widget.setPrecision(paramRanges['max']-paramRanges['min'], True) | |||||
| scalePoints = [] | |||||
| prefix = "" | |||||
| suffix = "" | |||||
| # NOTE: Issue #1983 | |||||
| # if ((paramData['hints'] & PARAMETER_USES_SCALEPOINTS) != 0): | |||||
| count = paramInfo['scalePointCount'] | |||||
| if count: | |||||
| for j in range(count): | |||||
| scalePoints.append(self.host.get_parameter_scalepoint_info(self.fPluginId, i, j)) | |||||
| setScalableDialStyle(widget, i, parameterCount, whiteLabels, self.fSkinStyle) | |||||
| prefix, suffix = getPrefixSuffix(paramInfo['unit']) | |||||
| widget.setScalePPS(sorted(scalePoints, key=operator.itemgetter("value")), prefix, suffix) | |||||
| index += 1 | index += 1 | ||||
| self.fParameterList.append([i, widget]) | self.fParameterList.append([i, widget]) | ||||
| layout.addWidget(widget) | layout.addWidget(widget) | ||||
| if self.w_knobs_right is not None and (self.fPluginInfo['hints'] & PLUGIN_CAN_DRYWET) != 0: | |||||
| widget = ScalableDial(self, PARAMETER_DRYWET) | |||||
| widget.setLabel("Dry/Wet") | |||||
| widget.setMinimum(0.0) | |||||
| widget.setMaximum(1.0) | |||||
| setScalableDialStyle(widget, PARAMETER_DRYWET, 0, whiteLabels, self.fSkinStyle) | |||||
| for i in range(index): | |||||
| widget = layout.itemAt(i).widget() | |||||
| if widget is not None: | |||||
| coef = i/(index-1) if index > 1 else 0.5 # 0.5 = Midrange | |||||
| hue = (hueFrom + coef * hueSpan) % 1.0 | |||||
| widget.setCustomPaintColor(QColor.fromHslF(hue, 1, 0.5, 1)) | |||||
| self.fParameterList.append([PARAMETER_DRYWET, widget]) | |||||
| self.w_knobs_right.layout().addWidget(widget) | |||||
| if self.w_knobs_right is not None and (self.fPluginInfo['hints'] & PLUGIN_CAN_VOLUME) != 0: | |||||
| widget = ScalableDial(self, PARAMETER_VOLUME) | |||||
| widget.setLabel("Volume") | |||||
| widget.setMinimum(0.0) | |||||
| widget.setMaximum(1.27) | |||||
| setScalableDialStyle(widget, PARAMETER_VOLUME, 0, whiteLabels, self.fSkinStyle) | |||||
| if self.w_knobs_right is not None: | |||||
| if (self.fPluginInfo['hints'] & PLUGIN_CAN_DRYWET) != 0: | |||||
| widget = ScalableDial(self, PARAMETER_DRYWET, 100, 1.0, 0.0, 1.0, "Dry/Wet", skinNum * 16 + ScalableDial.CUSTOM_PAINT_MODE_CARLA_WET, -1, "%", self.fSkinStyle, whiteLabels, self.fTweaks) | |||||
| self.fParameterList.append([PARAMETER_VOLUME, widget]) | |||||
| self.w_knobs_right.layout().addWidget(widget) | |||||
| self.fParameterList.append([PARAMETER_DRYWET, widget]) | |||||
| self.w_knobs_right.layout().addWidget(widget) | |||||
| if (self.fPluginInfo['hints'] & PLUGIN_CAN_VOLUME) != 0: | |||||
| widget = ScalableDial(self, PARAMETER_VOLUME, 127, 1.0, 0.0, 1.27, "Volume", skinNum * 16 + ScalableDial.CUSTOM_PAINT_MODE_CARLA_VOL, -1, "%", self.fSkinStyle, whiteLabels, self.fTweaks) | |||||
| self.fParameterList.append([PARAMETER_VOLUME, widget]) | |||||
| self.w_knobs_right.layout().addWidget(widget) | |||||
| if (self.fPluginInfo['hints'] & PLUGIN_CAN_PANNING) != 0: | |||||
| if widget.getTweak('ShowPan', 0): | |||||
| widget = ScalableDial(self, PARAMETER_PANNING, 100, 0.0, -1.0, 1.0, "Pan", skinNum * 16 + ScalableDial.CUSTOM_PAINT_MODE_CARLA_PAN, -1, "%", self.fSkinStyle, whiteLabels, self.fTweaks) | |||||
| self.fParameterList.append([PARAMETER_PANNING, widget]) | |||||
| self.w_knobs_right.layout().addWidget(widget) | |||||
| for paramIndex, paramWidget in self.fParameterList: | for paramIndex, paramWidget in self.fParameterList: | ||||
| paramWidget.setContextMenuPolicy(Qt.CustomContextMenu) | |||||
| paramWidget.customContextMenuRequested.connect(self.slot_knobCustomMenu) | |||||
| paramWidget.dragStateChanged.connect(self.slot_parameterDragStateChanged) | |||||
| paramWidget.realValueChanged.connect(self.slot_parameterValueChanged) | |||||
| if not paramWidget.fIsOutput: | |||||
| paramWidget.customContextMenuRequested.connect(self.slot_knobCustomMenu) | |||||
| paramWidget.dragStateChanged.connect(self.slot_parameterDragStateChanged) | |||||
| paramWidget.realValueChanged.connect(self.slot_parameterValueChanged) | |||||
| paramWidget.blockSignals(True) | paramWidget.blockSignals(True) | ||||
| paramWidget.setValue(self.host.get_internal_parameter_value(self.fPluginId, paramIndex)) | paramWidget.setValue(self.host.get_internal_parameter_value(self.fPluginId, paramIndex)) | ||||
| paramWidget.blockSignals(False) | paramWidget.blockSignals(False) | ||||
| @@ -724,6 +896,7 @@ class AbstractPluginSlot(QFrame, PluginEditParentMeta): | |||||
| elif parameterId == PARAMETER_CTRL_CHANNEL: | elif parameterId == PARAMETER_CTRL_CHANNEL: | ||||
| self.host.set_ctrl_channel(self.fPluginId, value) | self.host.set_ctrl_channel(self.fPluginId, value) | ||||
| self.setParameterValue(parameterId, value, True) | |||||
| self.fEditDialog.setParameterValue(parameterId, value) | self.fEditDialog.setParameterValue(parameterId, value) | ||||
| # ----------------------------------------------------------------- | # ----------------------------------------------------------------- | ||||
| @@ -891,13 +1064,18 @@ class AbstractPluginSlot(QFrame, PluginEditParentMeta): | |||||
| paramWidget.setVisible(hints & PLUGIN_CAN_DRYWET) | paramWidget.setVisible(hints & PLUGIN_CAN_DRYWET) | ||||
| elif paramIndex == PARAMETER_VOLUME: | elif paramIndex == PARAMETER_VOLUME: | ||||
| paramWidget.setVisible(hints & PLUGIN_CAN_VOLUME) | paramWidget.setVisible(hints & PLUGIN_CAN_VOLUME) | ||||
| # jpka: FIXME i add it, but can't trigger it for test, so disable to prevent possible crashes. Maybe it don't needed. | |||||
| # self.dial0.setVisible(hints & PLUGIN_CAN_DRYWET) | |||||
| # self.dial1.setVisible(hints & PLUGIN_CAN_VOLUME) | |||||
| # print("self.dial0.setVisible(hints & PLUGIN_CAN_DRYWET)") | |||||
| if self.b_gui is not None: | if self.b_gui is not None: | ||||
| self.b_gui.setEnabled(bool(hints & PLUGIN_HAS_CUSTOM_UI)) | self.b_gui.setEnabled(bool(hints & PLUGIN_HAS_CUSTOM_UI)) | ||||
| # NOTE: self.fParameterList is empty when compacted. | |||||
| def editDialogParameterValueChanged(self, pluginId, parameterId, value): | def editDialogParameterValueChanged(self, pluginId, parameterId, value): | ||||
| for paramIndex, paramWidget in self.fParameterList: | for paramIndex, paramWidget in self.fParameterList: | ||||
| if paramIndex != parameterId: | |||||
| if (paramIndex != parameterId) or paramWidget.fIsOutput: | |||||
| continue | continue | ||||
| paramWidget.blockSignals(True) | paramWidget.blockSignals(True) | ||||
| @@ -905,6 +1083,16 @@ class AbstractPluginSlot(QFrame, PluginEditParentMeta): | |||||
| paramWidget.blockSignals(False) | paramWidget.blockSignals(False) | ||||
| break | break | ||||
| if isinstance(self, PluginSlot_Compact): | |||||
| if (parameterId == PARAMETER_DRYWET) and (self.fPluginInfo['hints'] & PLUGIN_CAN_DRYWET): | |||||
| self.dial0.blockSignals(True) | |||||
| self.dial0.setValue(value) | |||||
| self.dial0.blockSignals(False) | |||||
| if (parameterId == PARAMETER_VOLUME) and (self.fPluginInfo['hints'] & PLUGIN_CAN_VOLUME): | |||||
| self.dial1.blockSignals(True) | |||||
| self.dial1.setValue(value) | |||||
| self.dial1.blockSignals(False) | |||||
| def editDialogProgramChanged(self, pluginId, index): | def editDialogProgramChanged(self, pluginId, index): | ||||
| if self.cb_presets is None: | if self.cb_presets is None: | ||||
| return | return | ||||
| @@ -997,6 +1185,23 @@ class AbstractPluginSlot(QFrame, PluginEditParentMeta): | |||||
| self.fEditDialog.idleSlow() | self.fEditDialog.idleSlow() | ||||
| # 7-seg displays are pretty effective, but added frame skip will make it even better. | |||||
| self.slowTimer = (self.slowTimer + 1) % 2 # Half the FPS, win some CPU. | |||||
| # NOTE: self.fParameterList is empty when compacted. | |||||
| for paramIndex, paramWidget in self.fParameterList: | |||||
| if not paramWidget.fIsOutput: | |||||
| continue | |||||
| # VU displays are CPU effective, make it run faster than 7-seg displays. | |||||
| # if (self.slowTimer > 0) and (not paramWidget.fIsVuOutput): | |||||
| if (self.slowTimer > 0): | |||||
| continue | |||||
| paramWidget.blockSignals(True) # TODO Is it required for output? | |||||
| value = self.host.get_current_parameter_value(self.fPluginId, paramIndex) | |||||
| paramWidget.setValue(value, False) | |||||
| paramWidget.blockSignals(False) | |||||
| # ----------------------------------------------------------------- | # ----------------------------------------------------------------- | ||||
| def drawOutline(self, painter): | def drawOutline(self, painter): | ||||
| @@ -1023,7 +1228,9 @@ class AbstractPluginSlot(QFrame, PluginEditParentMeta): | |||||
| def updateParameterValues(self): | def updateParameterValues(self): | ||||
| for paramIndex, paramWidget in self.fParameterList: | for paramIndex, paramWidget in self.fParameterList: | ||||
| if paramIndex < 0: | |||||
| if paramIndex < 0: # DryWet and Volume | |||||
| continue | |||||
| if paramWidget.fIsOutput: | |||||
| continue | continue | ||||
| paramWidget.blockSignals(True) | paramWidget.blockSignals(True) | ||||
| @@ -1044,8 +1251,9 @@ class AbstractPluginSlot(QFrame, PluginEditParentMeta): | |||||
| # Expand/Minimize and Tweaks | # Expand/Minimize and Tweaks | ||||
| actCompact = menu.addAction(self.tr("Expand") if isinstance(self, PluginSlot_Compact) else self.tr("Minimize")) | actCompact = menu.addAction(self.tr("Expand") if isinstance(self, PluginSlot_Compact) else self.tr("Minimize")) | ||||
| actColor = menu.addAction(self.tr("Change Color...")) | |||||
| actSkin = menu.addAction(self.tr("Change Skin...")) | |||||
| actColor = menu.addAction(self.tr("Change Color...")) | |||||
| actColorRandom = menu.addAction(self.tr("Random Color")) | |||||
| actSkin = menu.addAction(self.tr("Change Skin...")) | |||||
| menu.addSeparator() | menu.addSeparator() | ||||
| # ------------------------------------------------------------- | # ------------------------------------------------------------- | ||||
| @@ -1157,28 +1365,17 @@ class AbstractPluginSlot(QFrame, PluginEditParentMeta): | |||||
| colorStr = "%i;%i;%i" % color | colorStr = "%i;%i;%i" % color | ||||
| gCarla.gui.changePluginColor(self.fPluginId, color, colorStr) | gCarla.gui.changePluginColor(self.fPluginId, color, colorStr) | ||||
| elif actSel == actSkin: | |||||
| skinList = [ | |||||
| "default", | |||||
| "3bandeq", | |||||
| "rncbc", | |||||
| "calf_black", | |||||
| "calf_blue", | |||||
| "classic", | |||||
| "openav-old", | |||||
| "openav", | |||||
| "zynfx", | |||||
| "presets", | |||||
| "mpresets", | |||||
| ] | |||||
| try: | |||||
| index = skinList.index(self.fSkinStyle) | |||||
| except: | |||||
| index = 0 | |||||
| elif actSel == actColorRandom: | |||||
| hue = QColor(self.fSkinColor[0], self.fSkinColor[1], self.fSkinColor[2]).hueF() | |||||
| color = QColor.fromHslF((hue + random.random()*0.5 + 0.25) % 1.0, 0.25, 0.125, 1).getRgb()[0:3] | |||||
| colorStr = "%i;%i;%i" % color | |||||
| gCarla.gui.changePluginColor(self.fPluginId, color, colorStr) | |||||
| elif actSel == actSkin: | |||||
| skin = QInputDialog.getItem(self, self.tr("Change Skin"), | skin = QInputDialog.getItem(self, self.tr("Change Skin"), | ||||
| self.tr("Change Skin to:"), | self.tr("Change Skin to:"), | ||||
| skinList, index, False) | |||||
| skinList, arrayIndex(skinList, self.fSkinStyle), False) | |||||
| if not all(skin): | if not all(skin): | ||||
| return | return | ||||
| gCarla.gui.changePluginSkin(self.fPluginId, skin[0]) | gCarla.gui.changePluginSkin(self.fPluginId, skin[0]) | ||||
| @@ -1287,97 +1484,7 @@ class AbstractPluginSlot(QFrame, PluginEditParentMeta): | |||||
| @pyqtSlot() | @pyqtSlot() | ||||
| def slot_knobCustomMenu(self): | def slot_knobCustomMenu(self): | ||||
| sender = self.sender() | |||||
| index = sender.fIndex | |||||
| minimum = sender.fMinimum | |||||
| maximum = sender.fMaximum | |||||
| current = sender.fRealValue | |||||
| label = sender.fLabel | |||||
| if index in (PARAMETER_NULL, PARAMETER_CTRL_CHANNEL) or index <= PARAMETER_MAX: | |||||
| return | |||||
| elif index in (PARAMETER_DRYWET, PARAMETER_VOLUME): | |||||
| default = 1.0 | |||||
| elif index == PARAMETER_BALANCE_LEFT: | |||||
| default = -1.0 | |||||
| elif index == PARAMETER_BALANCE_RIGHT: | |||||
| default = 1.0 | |||||
| elif index == PARAMETER_PANNING: | |||||
| default = 0.0 | |||||
| else: | |||||
| default = self.host.get_default_parameter_value(self.fPluginId, index) | |||||
| if index < PARAMETER_NULL: | |||||
| # show in integer percentage | |||||
| textReset = self.tr("Reset (%i%%)" % round(default*100.0)) | |||||
| textMinim = self.tr("Set to Minimum (%i%%)" % round(minimum*100.0)) | |||||
| textMaxim = self.tr("Set to Maximum (%i%%)" % round(maximum*100.0)) | |||||
| else: | |||||
| # show in full float value | |||||
| textReset = self.tr("Reset (%f)" % default) | |||||
| textMinim = self.tr("Set to Minimum (%f)" % minimum) | |||||
| textMaxim = self.tr("Set to Maximum (%f)" % maximum) | |||||
| menu = QMenu(self) | |||||
| actReset = menu.addAction(textReset) | |||||
| menu.addSeparator() | |||||
| actMinimum = menu.addAction(textMinim) | |||||
| actCenter = menu.addAction(self.tr("Set to Center")) | |||||
| actMaximum = menu.addAction(textMaxim) | |||||
| menu.addSeparator() | |||||
| actSet = menu.addAction(self.tr("Set value...")) | |||||
| if index > PARAMETER_NULL or index not in (PARAMETER_BALANCE_LEFT, PARAMETER_BALANCE_RIGHT, PARAMETER_PANNING): | |||||
| menu.removeAction(actCenter) | |||||
| actSelected = menu.exec_(QCursor.pos()) | |||||
| if actSelected == actSet: | |||||
| if index < PARAMETER_NULL: | |||||
| value, ok = QInputDialog.getInt(self, self.tr("Set value"), label, round(current*100), round(minimum*100), round(maximum*100), 1) | |||||
| if not ok: | |||||
| return | |||||
| value = float(value)/100.0 | |||||
| else: | |||||
| paramInfo = self.host.get_parameter_info(self.fPluginId, index) | |||||
| paramRanges = self.host.get_parameter_ranges(self.fPluginId, index) | |||||
| scalePoints = [] | |||||
| for i in range(paramInfo['scalePointCount']): | |||||
| scalePoints.append(self.host.get_parameter_scalepoint_info(self.fPluginId, index, i)) | |||||
| prefix = "" | |||||
| suffix = paramInfo['unit'].strip() | |||||
| if suffix == "(coef)": | |||||
| prefix = "* " | |||||
| suffix = "" | |||||
| else: | |||||
| suffix = " " + suffix | |||||
| dialog = CustomInputDialog(self, label, current, minimum, maximum, | |||||
| paramRanges['step'], paramRanges['stepSmall'], scalePoints, prefix, suffix) | |||||
| if not dialog.exec_(): | |||||
| return | |||||
| value = dialog.returnValue() | |||||
| elif actSelected == actMinimum: | |||||
| value = minimum | |||||
| elif actSelected == actMaximum: | |||||
| value = maximum | |||||
| elif actSelected == actReset: | |||||
| value = default | |||||
| elif actSelected == actCenter: | |||||
| value = 0.0 | |||||
| else: | |||||
| return | |||||
| sender.setValue(value, True) | |||||
| PluginEdit.slot_knobCustomMenu(self) | |||||
| # ----------------------------------------------------------------- | # ----------------------------------------------------------------- | ||||
| @@ -1439,9 +1546,23 @@ class AbstractPluginSlot(QFrame, PluginEditParentMeta): | |||||
| if index < 0: | if index < 0: | ||||
| break | break | ||||
| curWidth += widget.width() + 4 | |||||
| if not widget.getIsVisible(): | |||||
| continue | |||||
| curWidth += widget.width() + RACK_KNOB_GAP | |||||
| if curWidth + widget.width() * 2 + 8 < maxWidth: | |||||
| if self.fTweaks.get('MoreSpace', 0): | |||||
| if self.w_knobs_right is None: # calf | |||||
| limit = curWidth | |||||
| else: | |||||
| if QT_VERSION < 0x60000: | |||||
| limit = curWidth + self.w_knobs_right.getContentsMargins()[0] + 8 | |||||
| else: | |||||
| limit = curWidth + 4 + 8 | |||||
| else: | |||||
| limit = curWidth + 56 + 8 | |||||
| if limit < maxWidth: | |||||
| #if not widget.isVisible(): | #if not widget.isVisible(): | ||||
| widget.show() | widget.show() | ||||
| continue | continue | ||||
| @@ -1719,6 +1840,12 @@ class PluginSlot_Compact(AbstractPluginSlot): | |||||
| self.peak_in = self.ui.peak_in | self.peak_in = self.ui.peak_in | ||||
| self.peak_out = self.ui.peak_out | self.peak_out = self.ui.peak_out | ||||
| if self.fTweaks.get('ShowProgramsOnCompact', 0): | |||||
| insertProgramList(self, self.ui.layout_peaks, 0) | |||||
| if self.fTweaks.get('ShowMidiProgramsOnCompact', 0): | |||||
| insertMidiProgramList(self, self.ui.layout_peaks, 0) | |||||
| self.ready() | self.ready() | ||||
| # ----------------------------------------------------------------- | # ----------------------------------------------------------------- | ||||
| @@ -1756,11 +1883,24 @@ class PluginSlot_Default(AbstractPluginSlot): | |||||
| self.w_knobs_right = self.ui.w_knobs_right | self.w_knobs_right = self.ui.w_knobs_right | ||||
| self.spacer_knobs = self.ui.layout_bottom.itemAt(1).spacerItem() | self.spacer_knobs = self.ui.layout_bottom.itemAt(1).spacerItem() | ||||
| if self.fTweaks.get('MoreSpace', 0): | |||||
| self.ui.layout_bottom.setContentsMargins(0, 4, 0, 0) | |||||
| if self.fTweaks.get('ShowPrograms', 0): | |||||
| # insertProgramList(self, self.ui.layout_top, 6) | |||||
| insertProgramList(self, self.ui.layout_peaks, 0) | |||||
| if self.fTweaks.get('ShowMidiPrograms', 0): | |||||
| # insertMidiProgramList(self, self.ui.layout_top, 6) | |||||
| insertMidiProgramList(self, self.ui.layout_peaks, 0) | |||||
| self.ready() | self.ready() | ||||
| # ----------------------------------------------------------------- | # ----------------------------------------------------------------- | ||||
| def getFixedHeight(self): | def getFixedHeight(self): | ||||
| if self.fSkinStyle == "tube": | |||||
| return 98 | |||||
| return 80 | return 80 | ||||
| # ----------------------------------------------------------------- | # ----------------------------------------------------------------- | ||||
| @@ -1839,13 +1979,17 @@ class PluginSlot_Presets(AbstractPluginSlot): | |||||
| self.peak_in = self.ui.peak_in | self.peak_in = self.ui.peak_in | ||||
| self.peak_out = self.ui.peak_out | self.peak_out = self.ui.peak_out | ||||
| if skinStyle == "zynfx": | |||||
| # if skinStyle == "zynfx": # TODO jpka: TEST ing zynfx as normal tweakable skin | |||||
| if False: | |||||
| self.setupZynFxParams() | self.setupZynFxParams() | ||||
| else: | else: | ||||
| self.w_knobs_left = self.ui.w_knobs_left | self.w_knobs_left = self.ui.w_knobs_left | ||||
| self.w_knobs_right = self.ui.w_knobs_right | self.w_knobs_right = self.ui.w_knobs_right | ||||
| self.spacer_knobs = self.ui.layout_bottom.itemAt(1).spacerItem() | self.spacer_knobs = self.ui.layout_bottom.itemAt(1).spacerItem() | ||||
| if self.fTweaks.get('MoreSpace', 0): | |||||
| self.ui.layout_bottom.setContentsMargins(0, 4, 0, 0) | |||||
| self.ready() | self.ready() | ||||
| if usingMidiPrograms: | if usingMidiPrograms: | ||||
| @@ -1855,6 +1999,8 @@ class PluginSlot_Presets(AbstractPluginSlot): | |||||
| # ------------------------------------------------------------- | # ------------------------------------------------------------- | ||||
| # it works only for internal zyn builds, which are disabled by default | |||||
| # (?) not for just manual "zynfx" skin selection | |||||
| def setupZynFxParams(self): | def setupZynFxParams(self): | ||||
| parameterCount = min(self.host.get_parameter_count(self.fPluginId), 8) | parameterCount = min(self.host.get_parameter_count(self.fPluginId), 8) | ||||
| @@ -2001,6 +2147,46 @@ class PluginSlot_Presets(AbstractPluginSlot): | |||||
| # ------------------------------------------------------------------------------------------------------------ | # ------------------------------------------------------------------------------------------------------------ | ||||
| def insertProgramList(self, layout, index): | |||||
| count = self.host.get_program_count(self.fPluginId) | |||||
| if count: | |||||
| cb = QComboBox(None) | |||||
| cb.setObjectName("cb_presets0") # use this stylesheet | |||||
| for i in range(count): | |||||
| string = self.host.get_program_name(self.fPluginId, i) | |||||
| if len(string) == 0: | |||||
| print("Carla: WARNING: Program List have zero length item.") | |||||
| return | |||||
| cb.addItem(string) | |||||
| layout.insertWidget(index, cb) | |||||
| cb.setCurrentIndex(self.host.get_current_program_index(self.fPluginId)) | |||||
| cb.currentIndexChanged.connect(self.slot_programChanged) | |||||
| def insertMidiProgramList(self, layout, index): | |||||
| count = self.host.get_midi_program_count(self.fPluginId) | |||||
| if count: | |||||
| cb = QComboBox(None) | |||||
| cb.setObjectName("cb_presets1") # use this stylesheet | |||||
| for i in range(count): | |||||
| string = self.host.get_midi_program_data(self.fPluginId, i)['name'] | |||||
| if len(string) == 0: | |||||
| print("Carla: WARNING: MIDI Program List have zero length item.") | |||||
| return | |||||
| cb.addItem(string) | |||||
| layout.insertWidget(index, cb) | |||||
| cb.setCurrentIndex(self.host.get_current_midi_program_index(self.fPluginId)) | |||||
| cb.currentIndexChanged.connect(self.slot_midiProgramChanged) | |||||
| # ------------------------------------------------------------------------------------------------------------ | |||||
| def getColorAndSkinStyle(host, pluginId): | def getColorAndSkinStyle(host, pluginId): | ||||
| pluginInfo = host.get_plugin_info(pluginId) | pluginInfo = host.get_plugin_info(pluginId) | ||||
| pluginName = host.get_real_plugin_name(pluginId) | pluginName = host.get_real_plugin_name(pluginId) | ||||
| @@ -2018,9 +2204,9 @@ def getColorAndSkinStyle(host, pluginId): | |||||
| # Samplers | # Samplers | ||||
| if pluginInfo['type'] == PLUGIN_SF2: | if pluginInfo['type'] == PLUGIN_SF2: | ||||
| return (colorCategory, "sf2") | |||||
| return (colorCategory, "mpresets") | |||||
| if pluginInfo['type'] == PLUGIN_SFZ: | if pluginInfo['type'] == PLUGIN_SFZ: | ||||
| return (colorCategory, "sfz") | |||||
| return (colorCategory, "mpresets") | |||||
| # Calf | # Calf | ||||
| if pluginName.split(" ", 1)[0].lower() == "calf": | if pluginName.split(" ", 1)[0].lower() == "calf": | ||||
| @@ -2032,6 +2218,10 @@ def getColorAndSkinStyle(host, pluginId): | |||||
| if pluginMaker == "OpenAV": | if pluginMaker == "OpenAV": | ||||
| return (colorNone, "openav") | return (colorNone, "openav") | ||||
| # Tube | |||||
| if "tube" in pluginLabel: | |||||
| return (colorCategory, "tube") | |||||
| # ZynFX | # ZynFX | ||||
| if pluginInfo['type'] == PLUGIN_INTERNAL: | if pluginInfo['type'] == PLUGIN_INTERNAL: | ||||
| if pluginLabel.startswith("zyn") and pluginInfo['category'] != PLUGIN_CATEGORY_SYNTH: | if pluginLabel.startswith("zyn") and pluginInfo['category'] != PLUGIN_CATEGORY_SYNTH: | ||||
| @@ -2042,7 +2232,8 @@ def getColorAndSkinStyle(host, pluginId): | |||||
| return (colorNone, "zynfx") | return (colorNone, "zynfx") | ||||
| if pluginInfo['type'] == PLUGIN_LV2: | if pluginInfo['type'] == PLUGIN_LV2: | ||||
| if pluginLabel.startswith("http://kxstudio.sf.net/carla/plugins/zyn") and pluginName != "ZynAddSubFX": | |||||
| # if pluginLabel.startswith("http://kxstudio.sf.net/carla/plugins/zyn") and pluginName != "ZynAddSubFX": | |||||
| if pluginLabel.startswith("http://kxstudio.sf.net/carla/plugins/zyn") and pluginName != "ZynAddSubFX" or "zyn" in pluginLabel: # jpka: TEST ing zynfx as normal tweakable skin | |||||
| return (colorNone, "zynfx") | return (colorNone, "zynfx") | ||||
| # Presets | # Presets | ||||
| @@ -14,7 +14,7 @@ from qt_compat import qt_config | |||||
| if qt_config == 5: | if qt_config == 5: | ||||
| from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QByteArray | from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QByteArray | ||||
| from PyQt5.QtGui import QCursor, QIcon, QPalette, QPixmap | |||||
| from PyQt5.QtGui import QCursor, QIcon, QPalette, QPixmap, QFont | |||||
| from PyQt5.QtWidgets import ( | from PyQt5.QtWidgets import ( | ||||
| QDialog, | QDialog, | ||||
| QFileDialog, | QFileDialog, | ||||
| @@ -24,10 +24,18 @@ if qt_config == 5: | |||||
| QScrollArea, | QScrollArea, | ||||
| QVBoxLayout, | QVBoxLayout, | ||||
| QWidget, | QWidget, | ||||
| QGraphicsScene, | |||||
| QGraphicsTextItem, | |||||
| QGraphicsView, | |||||
| QTableWidget, | |||||
| QTableWidgetItem, | |||||
| QHeaderView, | |||||
| QLabel, | |||||
| QSizePolicy, | |||||
| ) | ) | ||||
| elif qt_config == 6: | elif qt_config == 6: | ||||
| from PyQt6.QtCore import pyqtSignal, pyqtSlot, Qt, QByteArray | from PyQt6.QtCore import pyqtSignal, pyqtSlot, Qt, QByteArray | ||||
| from PyQt6.QtGui import QCursor, QIcon, QPalette, QPixmap | |||||
| from PyQt6.QtGui import QCursor, QIcon, QPalette, QPixmap, QFont | |||||
| from PyQt6.QtWidgets import ( | from PyQt6.QtWidgets import ( | ||||
| QDialog, | QDialog, | ||||
| QFileDialog, | QFileDialog, | ||||
| @@ -37,6 +45,14 @@ elif qt_config == 6: | |||||
| QScrollArea, | QScrollArea, | ||||
| QVBoxLayout, | QVBoxLayout, | ||||
| QWidget, | QWidget, | ||||
| QGraphicsScene, | |||||
| QGraphicsTextItem, | |||||
| QGraphicsView, | |||||
| QTableWidget, | |||||
| QTableWidgetItem, | |||||
| QHeaderView, | |||||
| QLabel, | |||||
| QSizePolicy, | |||||
| ) | ) | ||||
| # ------------------------------------------------------------------------------------------------------------ | # ------------------------------------------------------------------------------------------------------------ | ||||
| @@ -72,6 +88,7 @@ from carla_backend import ( | |||||
| PLUGIN_OPTION_SEND_ALL_SOUND_OFF, | PLUGIN_OPTION_SEND_ALL_SOUND_OFF, | ||||
| PLUGIN_OPTION_SEND_PROGRAM_CHANGES, | PLUGIN_OPTION_SEND_PROGRAM_CHANGES, | ||||
| PLUGIN_OPTION_SKIP_SENDING_NOTES, | PLUGIN_OPTION_SKIP_SENDING_NOTES, | ||||
| PARAMETER_NULL, | |||||
| PARAMETER_DRYWET, | PARAMETER_DRYWET, | ||||
| PARAMETER_VOLUME, | PARAMETER_VOLUME, | ||||
| PARAMETER_BALANCE_LEFT, | PARAMETER_BALANCE_LEFT, | ||||
| @@ -84,6 +101,7 @@ from carla_backend import ( | |||||
| PARAMETER_USES_SCALEPOINTS, | PARAMETER_USES_SCALEPOINTS, | ||||
| PARAMETER_USES_CUSTOM_TEXT, | PARAMETER_USES_CUSTOM_TEXT, | ||||
| PARAMETER_CAN_BE_CV_CONTROLLED, | PARAMETER_CAN_BE_CV_CONTROLLED, | ||||
| parameterHintsText, | |||||
| PARAMETER_INPUT, PARAMETER_OUTPUT, | PARAMETER_INPUT, PARAMETER_OUTPUT, | ||||
| CONTROL_INDEX_NONE, | CONTROL_INDEX_NONE, | ||||
| CONTROL_INDEX_MIDI_PITCHBEND, | CONTROL_INDEX_MIDI_PITCHBEND, | ||||
| @@ -94,6 +112,7 @@ from carla_backend import ( | |||||
| from carla_shared import ( | from carla_shared import ( | ||||
| MIDI_CC_LIST, MAX_MIDI_CC_LIST_ITEM, | MIDI_CC_LIST, MAX_MIDI_CC_LIST_ITEM, | ||||
| countDecimalPoints, | countDecimalPoints, | ||||
| strLim, | |||||
| fontMetricsHorizontalAdvance, | fontMetricsHorizontalAdvance, | ||||
| setUpSignals, | setUpSignals, | ||||
| gCarla | gCarla | ||||
| @@ -103,6 +122,8 @@ from carla_utils import getPluginTypeAsString | |||||
| from widgets.collapsablewidget import CollapsibleBox | from widgets.collapsablewidget import CollapsibleBox | ||||
| from widgets.pixmapkeyboard import PixmapKeyboardHArea | from widgets.pixmapkeyboard import PixmapKeyboardHArea | ||||
| from widgets.paramspinbox import CustomInputDialog, ParamSpinBox | |||||
| from widgets.scalabledial import ScalableDial | |||||
| # ------------------------------------------------------------------------------------------------------------ | # ------------------------------------------------------------------------------------------------------------ | ||||
| # Carla GUI defines | # Carla GUI defines | ||||
| @@ -119,6 +140,7 @@ class PluginParameter(QWidget): | |||||
| mappedControlChanged = pyqtSignal(int, int) | mappedControlChanged = pyqtSignal(int, int) | ||||
| mappedRangeChanged = pyqtSignal(int, float, float) | mappedRangeChanged = pyqtSignal(int, float, float) | ||||
| midiChannelChanged = pyqtSignal(int, int) | midiChannelChanged = pyqtSignal(int, int) | ||||
| knobVisibilityChanged = pyqtSignal(int, int) | |||||
| valueChanged = pyqtSignal(int, float) | valueChanged = pyqtSignal(int, float) | ||||
| def __init__(self, parent, host, pInfo, pluginId, tabIndex): | def __init__(self, parent, host, pInfo, pluginId, tabIndex): | ||||
| @@ -141,6 +163,7 @@ class PluginParameter(QWidget): | |||||
| self.fParameterId = pInfo['index'] | self.fParameterId = pInfo['index'] | ||||
| self.fPluginId = pluginId | self.fPluginId = pluginId | ||||
| self.fTabIndex = tabIndex | self.fTabIndex = tabIndex | ||||
| self.fKnobVisible = True | |||||
| # ------------------------------------------------------------- | # ------------------------------------------------------------- | ||||
| # Set-up GUI | # Set-up GUI | ||||
| @@ -157,6 +180,7 @@ class PluginParameter(QWidget): | |||||
| self.ui.widget.setStep(pInfo['step']) | self.ui.widget.setStep(pInfo['step']) | ||||
| self.ui.widget.setStepSmall(pInfo['stepSmall']) | self.ui.widget.setStepSmall(pInfo['stepSmall']) | ||||
| self.ui.widget.setStepLarge(pInfo['stepLarge']) | self.ui.widget.setStepLarge(pInfo['stepLarge']) | ||||
| # NOTE: Issue #1983 | |||||
| self.ui.widget.setScalePoints(pInfo['scalePoints'], bool(pHints & PARAMETER_USES_SCALEPOINTS)) | self.ui.widget.setScalePoints(pInfo['scalePoints'], bool(pHints & PARAMETER_USES_SCALEPOINTS)) | ||||
| if pInfo['comment']: | if pInfo['comment']: | ||||
| @@ -536,39 +560,21 @@ class PluginEdit(QDialog): | |||||
| labelPluginFont.setWeight(75) | labelPluginFont.setWeight(75) | ||||
| self.ui.label_plugin.setFont(labelPluginFont) | self.ui.label_plugin.setFont(labelPluginFont) | ||||
| self.ui.dial_drywet.setCustomPaintMode(self.ui.dial_drywet.CUSTOM_PAINT_MODE_CARLA_WET) | |||||
| self.ui.dial_drywet.setImage(3) | |||||
| self.ui.dial_drywet.setLabel("Dry/Wet") | |||||
| self.ui.dial_drywet.setMinimum(0.0) | |||||
| self.ui.dial_drywet.setMaximum(1.0) | |||||
| pluginHints = self.host.get_plugin_info(self.fPluginId)['hints'] | |||||
| self.ui.dial_drywet = ScalableDial(self.ui.dial_drywet, PARAMETER_DRYWET, 100, 1.0, 0.0, 1.0, "Dry/Wet", ScalableDial.CUSTOM_PAINT_MODE_CARLA_WET) | |||||
| self.ui.dial_drywet.setValue(host.get_internal_parameter_value(pluginId, PARAMETER_DRYWET)) | self.ui.dial_drywet.setValue(host.get_internal_parameter_value(pluginId, PARAMETER_DRYWET)) | ||||
| self.ui.dial_vol.setCustomPaintMode(self.ui.dial_vol.CUSTOM_PAINT_MODE_CARLA_VOL) | |||||
| self.ui.dial_vol.setImage(3) | |||||
| self.ui.dial_vol.setLabel("Volume") | |||||
| self.ui.dial_vol.setMinimum(0.0) | |||||
| self.ui.dial_vol.setMaximum(1.27) | |||||
| self.ui.dial_vol = ScalableDial(self.ui.dial_vol, PARAMETER_VOLUME, 127, 1.0, 0.0, 1.27, "Volume", ScalableDial.CUSTOM_PAINT_MODE_CARLA_VOL) | |||||
| self.ui.dial_vol.setValue(host.get_internal_parameter_value(pluginId, PARAMETER_VOLUME)) | self.ui.dial_vol.setValue(host.get_internal_parameter_value(pluginId, PARAMETER_VOLUME)) | ||||
| self.ui.dial_b_left.setCustomPaintMode(self.ui.dial_b_left.CUSTOM_PAINT_MODE_CARLA_L) | |||||
| self.ui.dial_b_left.setImage(4) | |||||
| self.ui.dial_b_left.setLabel("L") | |||||
| self.ui.dial_b_left.setMinimum(-1.0) | |||||
| self.ui.dial_b_left.setMaximum(1.0) | |||||
| self.ui.dial_b_left = ScalableDial(self.ui.dial_b_left, PARAMETER_BALANCE_LEFT, 100, -1.0, -1.0, 1.0, "L", ScalableDial.CUSTOM_PAINT_MODE_CARLA_L) | |||||
| self.ui.dial_b_left.setValue(host.get_internal_parameter_value(pluginId, PARAMETER_BALANCE_LEFT)) | self.ui.dial_b_left.setValue(host.get_internal_parameter_value(pluginId, PARAMETER_BALANCE_LEFT)) | ||||
| self.ui.dial_b_right.setCustomPaintMode(self.ui.dial_b_right.CUSTOM_PAINT_MODE_CARLA_R) | |||||
| self.ui.dial_b_right.setImage(4) | |||||
| self.ui.dial_b_right.setLabel("R") | |||||
| self.ui.dial_b_right.setMinimum(-1.0) | |||||
| self.ui.dial_b_right.setMaximum(1.0) | |||||
| self.ui.dial_b_right = ScalableDial(self.ui.dial_b_right, PARAMETER_BALANCE_RIGHT, 100, 1.0, -1.0, 1.0, "R", ScalableDial.CUSTOM_PAINT_MODE_CARLA_R) | |||||
| self.ui.dial_b_right.setValue(host.get_internal_parameter_value(pluginId, PARAMETER_BALANCE_RIGHT)) | self.ui.dial_b_right.setValue(host.get_internal_parameter_value(pluginId, PARAMETER_BALANCE_RIGHT)) | ||||
| self.ui.dial_pan.setCustomPaintMode(self.ui.dial_b_right.CUSTOM_PAINT_MODE_CARLA_PAN) | |||||
| self.ui.dial_pan.setImage(4) | |||||
| self.ui.dial_pan.setLabel("Pan") | |||||
| self.ui.dial_pan.setMinimum(-1.0) | |||||
| self.ui.dial_pan.setMaximum(1.0) | |||||
| self.ui.dial_pan = ScalableDial(self.ui.dial_pan, PARAMETER_PANNING, 100, 0, -1.0, 1.0, "Pan", ScalableDial.CUSTOM_PAINT_MODE_CARLA_PAN) | |||||
| self.ui.dial_pan.setValue(host.get_internal_parameter_value(pluginId, PARAMETER_PANNING)) | self.ui.dial_pan.setValue(host.get_internal_parameter_value(pluginId, PARAMETER_PANNING)) | ||||
| self.ui.sb_ctrl_channel.setValue(self.fControlChannel+1) | self.ui.sb_ctrl_channel.setValue(self.fControlChannel+1) | ||||
| @@ -582,10 +588,10 @@ class PluginEdit(QDialog): | |||||
| self.ui.scrollArea.setVisible(False) | self.ui.scrollArea.setVisible(False) | ||||
| # todo | # todo | ||||
| self.ui.rb_balance.setEnabled(False) | |||||
| self.ui.rb_balance.setVisible(False) | |||||
| self.ui.rb_pan.setEnabled(False) | |||||
| self.ui.rb_pan.setVisible(False) | |||||
| # self.ui.rb_balance.setEnabled(False) | |||||
| # self.ui.rb_balance.setVisible(False) | |||||
| # self.ui.rb_pan.setEnabled(False) | |||||
| # self.ui.rb_pan.setVisible(False) | |||||
| flags = self.windowFlags() | flags = self.windowFlags() | ||||
| flags &= ~Qt.WindowContextHelpButtonHint | flags &= ~Qt.WindowContextHelpButtonHint | ||||
| @@ -901,7 +907,6 @@ class PluginEdit(QDialog): | |||||
| break | break | ||||
| paramData = self.host.get_parameter_data(self.fPluginId, i) | paramData = self.host.get_parameter_data(self.fPluginId, i) | ||||
| if paramData['type'] not in (PARAMETER_INPUT, PARAMETER_OUTPUT): | if paramData['type'] not in (PARAMETER_INPUT, PARAMETER_OUTPUT): | ||||
| unusedParameters += 1 | unusedParameters += 1 | ||||
| continue | continue | ||||
| @@ -974,6 +979,12 @@ class PluginEdit(QDialog): | |||||
| self._createParameterWidgets(PARAMETER_INPUT, paramInputListFull, self.tr("Parameters")) | self._createParameterWidgets(PARAMETER_INPUT, paramInputListFull, self.tr("Parameters")) | ||||
| self._createParameterWidgets(PARAMETER_OUTPUT, paramOutputListFull, self.tr("Outputs")) | self._createParameterWidgets(PARAMETER_OUTPUT, paramOutputListFull, self.tr("Outputs")) | ||||
| # Create full parameter list table tab | |||||
| self._createParameterXrayTab(self.tr("XRay")) | |||||
| # Create experimental description tab WORKINPROGRESS NOTE discussions/1967, pull/1961 | |||||
| self._createDescriptionTab(self.tr("Description")) | |||||
| # Restore tab state | # Restore tab state | ||||
| if tabIndex < self.ui.tabWidget.count(): | if tabIndex < self.ui.tabWidget.count(): | ||||
| self.ui.tabWidget.setCurrentIndex(tabIndex) | self.ui.tabWidget.setCurrentIndex(tabIndex) | ||||
| @@ -1474,69 +1485,67 @@ class PluginEdit(QDialog): | |||||
| @pyqtSlot() | @pyqtSlot() | ||||
| def slot_knobCustomMenu(self): | def slot_knobCustomMenu(self): | ||||
| sender = self.sender() | |||||
| knobName = sender.objectName() | |||||
| if knobName == "dial_drywet": | |||||
| minimum = 0.0 | |||||
| maximum = 1.0 | |||||
| default = 1.0 | |||||
| label = "Dry/Wet" | |||||
| elif knobName == "dial_vol": | |||||
| minimum = 0.0 | |||||
| maximum = 1.27 | |||||
| default = 1.0 | |||||
| label = "Volume" | |||||
| elif knobName == "dial_b_left": | |||||
| minimum = -1.0 | |||||
| maximum = 1.0 | |||||
| default = -1.0 | |||||
| label = "Balance-Left" | |||||
| elif knobName == "dial_b_right": | |||||
| minimum = -1.0 | |||||
| maximum = 1.0 | |||||
| default = 1.0 | |||||
| label = "Balance-Right" | |||||
| elif knobName == "dial_pan": | |||||
| minimum = -1.0 | |||||
| maximum = 1.0 | |||||
| default = 0.0 | |||||
| label = "Panning" | |||||
| # jpka: NOTE now Edit knobs are also know their constraints, so it's worth to set values as normal ones. | |||||
| sender = self.sender() | |||||
| index = sender.fIndex | |||||
| minimum = sender.fMinimum | |||||
| maximum = sender.fMaximum | |||||
| current = sender.fRealValue | |||||
| label = sender.fLabel | |||||
| default = sender.fDefault | |||||
| unit = sender.fUnit | |||||
| step = stepSmall = 1.0 | |||||
| if index < PARAMETER_NULL: | |||||
| percent = 100.0 | |||||
| else: | else: | ||||
| minimum = 0.0 | |||||
| maximum = 1.0 | |||||
| default = 0.5 | |||||
| label = "Unknown" | |||||
| percent = 1 | |||||
| textReset = self.tr("Reset (" + strLim(default * percent) + unit + ")\tR, Middle click") | |||||
| textMinim = self.tr("Set to Minimum (" + strLim(minimum * percent) + unit + ")\t0") | |||||
| textMaxim = self.tr("Set to Maximum (" + strLim(maximum * percent) + unit + ")\tEnd") | |||||
| if sender.fIsButton: | |||||
| editHotKey = "E" | |||||
| else: | |||||
| editHotKey = "Enter, Double click" | |||||
| menu = QMenu(self) | menu = QMenu(self) | ||||
| actReset = menu.addAction(self.tr("Reset (%i%%)" % (default*100))) | |||||
| actReset = menu.addAction(textReset) | |||||
| menu.addSeparator() | menu.addSeparator() | ||||
| actMinimum = menu.addAction(self.tr("Set to Minimum (%i%%)" % (minimum*100))) | |||||
| actCenter = menu.addAction(self.tr("Set to Center")) | |||||
| actMaximum = menu.addAction(self.tr("Set to Maximum (%i%%)" % (maximum*100))) | |||||
| actMinimum = menu.addAction(textMinim) | |||||
| actCenter = menu.addAction(self.tr("Set to Center\t5")) | |||||
| actMaximum = menu.addAction(textMaxim) | |||||
| menu.addSeparator() | menu.addSeparator() | ||||
| actSet = menu.addAction(self.tr("Set value...")) | |||||
| actSet = menu.addAction(self.tr("Set value...\t" + editHotKey)) | |||||
| if label not in ("Balance-Left", "Balance-Right", "Panning"): | |||||
| if index > PARAMETER_NULL or index not in (PARAMETER_BALANCE_LEFT, PARAMETER_BALANCE_RIGHT, PARAMETER_PANNING): | |||||
| menu.removeAction(actCenter) | menu.removeAction(actCenter) | ||||
| actSelected = menu.exec_(QCursor.pos()) | actSelected = menu.exec_(QCursor.pos()) | ||||
| if actSelected == actSet: | if actSelected == actSet: | ||||
| current = minimum + (maximum-minimum)*(float(sender.value())/10000) | |||||
| value, ok = QInputDialog.getInt(self, | |||||
| self.tr("Set value"), | |||||
| label, | |||||
| round(current*100.0), | |||||
| round(minimum*100.0), | |||||
| round(maximum*100.0), | |||||
| 1) | |||||
| if ok: | |||||
| value = float(value)/100.0 | |||||
| paramInfo = self.host.get_parameter_info(self.fPluginId, index) | |||||
| paramRanges = self.host.get_parameter_ranges(self.fPluginId, index) | |||||
| scalePoints = [] | |||||
| if not ok: | |||||
| for i in range(paramInfo['scalePointCount']): | |||||
| scalePoints.append(self.host.get_parameter_scalepoint_info(self.fPluginId, index, i)) | |||||
| if sender.fIsInteger: | |||||
| step = max(1, int((maximum - minimum)/100)) | |||||
| stepSmall = max(1, int(step/10)) | |||||
| else: | |||||
| step = paramRanges['step'] * percent | |||||
| stepSmall = paramRanges['stepSmall'] * percent | |||||
| dialog = CustomInputDialog(self, label, current * percent, minimum * percent, maximum * percent, step, stepSmall, scalePoints, "", "", unit) | |||||
| if not dialog.exec_(): | |||||
| return | return | ||||
| value = dialog.returnValue() / percent | |||||
| elif actSelected == actMinimum: | elif actSelected == actMinimum: | ||||
| value = minimum | value = minimum | ||||
| elif actSelected == actMaximum: | elif actSelected == actMaximum: | ||||
| @@ -1605,13 +1614,15 @@ class PluginEdit(QDialog): | |||||
| scrollAreaLayout = QVBoxLayout(scrollAreaWidget) | scrollAreaLayout = QVBoxLayout(scrollAreaWidget) | ||||
| scrollAreaLayout.setSpacing(3) | scrollAreaLayout.setSpacing(3) | ||||
| expandBox = (len(paramList) < 50) | |||||
| for paramInfo in paramList: | for paramInfo in paramList: | ||||
| groupName = paramInfo['groupName'] | groupName = paramInfo['groupName'] | ||||
| if groupName: | if groupName: | ||||
| groupSymbol, groupName = groupName.split(":",1) | groupSymbol, groupName = groupName.split(":",1) | ||||
| groupLayout, groupWidget = groupWidgets.get(groupSymbol, (None, None)) | groupLayout, groupWidget = groupWidgets.get(groupSymbol, (None, None)) | ||||
| if groupLayout is None: | if groupLayout is None: | ||||
| groupWidget = CollapsibleBox(groupName, scrollAreaWidget) | |||||
| groupWidget = CollapsibleBox(groupName, scrollAreaWidget, expandBox) | |||||
| groupLayout = groupWidget.getContentLayout() | groupLayout = groupWidget.getContentLayout() | ||||
| groupWidget.setPalette(palette2) | groupWidget.setPalette(palette2) | ||||
| scrollAreaLayout.addWidget(groupWidget) | scrollAreaLayout.addWidget(groupWidget) | ||||
| @@ -1678,6 +1689,140 @@ class PluginEdit(QDialog): | |||||
| #------------------------------------------------------------------ | #------------------------------------------------------------------ | ||||
| # NOTE To speed things up, displayed data is not realtime. Reopen project to expose last changes. | |||||
| def _createParameterXrayTab(self, tabName): | |||||
| # How simple would be fit a value into cell? Yet to be as fast as we can. | |||||
| def strFit(value): | |||||
| if isinstance(value, str): | |||||
| return value | |||||
| # For 'carla-control', but anyway scalePoints are not work. #1984 | |||||
| elif isinstance(value, list): | |||||
| return str(value) # It's [] | |||||
| elif abs(value) >= 1E8: | |||||
| return '{:.3e}'.format(value) | |||||
| elif value == int(value): # Zero falls here | |||||
| return str(int(value)) | |||||
| else: | |||||
| return strLim(value) | |||||
| def strLineWrap(string, cut): | |||||
| result = '' | |||||
| while len(string) > cut: # FIXME Optimize me! | |||||
| result += string[:cut] + '\n' | |||||
| string = string[cut:] | |||||
| result += string | |||||
| return result | |||||
| def addCell(section, name, string, toolTip = ''): | |||||
| if x == table.columnCount(): | |||||
| table.insertColumn(x) | |||||
| # jpka: FIXME Here we need vertical text. But impossible, no working examples. | |||||
| # Only untested https://stackoverflow.com/questions/52162125/ | |||||
| nameWrapped = section + '\n' + strLineWrap(name, 6) | |||||
| table.setHorizontalHeaderItem(x, QTableWidgetItem(nameWrapped)) | |||||
| table.horizontalHeader().setSectionResizeMode(x, QHeaderView.ResizeToContents) | |||||
| if y == table.rowCount(): | |||||
| table.insertRow(y) | |||||
| table.verticalHeader().setSectionResizeMode(y, QHeaderView.ResizeToContents) | |||||
| item = QTableWidgetItem(string) | |||||
| if toolTip: | |||||
| item.setToolTip(toolTip) | |||||
| table.setItem(y, x, item) | |||||
| return | |||||
| table = QTableWidget(self) | |||||
| table.setObjectName("table") | |||||
| table.setRowCount(1) | |||||
| # table.setToolTipDuration(2000) | |||||
| parameterCount = self.host.get_parameter_count(self.fPluginId) | |||||
| if parameterCount <= 0: | |||||
| return | |||||
| y = 0 | |||||
| for i in range(parameterCount): | |||||
| x = 0 | |||||
| param = self.host.get_parameter_data(self.fPluginId, i) | |||||
| for name in param: | |||||
| value = param[name] | |||||
| if (name == 'type') and (value in (1, 2,)): | |||||
| addCell('Data', name, str(value) + (' in',' out')[value - 1]) | |||||
| elif (name == 'hints'): | |||||
| # toolTip = '<body>' + bin(value)[2:] | |||||
| hints = '' | |||||
| for bit in range(len(parameterHintsText)): | |||||
| if (value & int(2**(bit-1))): | |||||
| hint = parameterHintsText[bit-1] | |||||
| # toolTip += '<br>' + hint | |||||
| hints += ', ' + hint | |||||
| addCell('Data', name, str(value)) # , toolTip + '</body>') | |||||
| x += 1 | |||||
| addCell('Hints', '', hints[2:]) | |||||
| else: | |||||
| addCell('Data', name, strFit(value)) | |||||
| x += 1 | |||||
| param = self.host.get_parameter_ranges(self.fPluginId, i) | |||||
| for name in param: | |||||
| addCell('Ranges', name, strFit(param[name])) | |||||
| x += 1 | |||||
| param = self.host.get_parameter_info(self.fPluginId, i) | |||||
| for name in param: | |||||
| addCell('Info', name, strFit(param[name])) | |||||
| x += 1 | |||||
| strScalePoints = '' | |||||
| for j in range(param['scalePointCount']): | |||||
| scalePointInfo = self.host.get_parameter_scalepoint_info(self.fPluginId, i, j) | |||||
| strScalePoints += (strFit(scalePointInfo['value']) + ':' + scalePointInfo['label'] + ',') | |||||
| if strScalePoints: | |||||
| addCell('Scalepoint_info', 'Scale Points', strLineWrap(strScalePoints[:len(strScalePoints)-1], 80)) | |||||
| x += 1 | |||||
| y += 1 | |||||
| self.ui.tabWidget.addTab(table, tabName) | |||||
| # self.ui.tabWidget.setToolTipDuration(2000) | |||||
| #------------------------------------------------------------------ | |||||
| def _createDescriptionTab(self, tabPageName): | |||||
| # jpka: To be filled from 'rdfs:comment' | |||||
| strDescr = "To be filled from rdfs:comment" | |||||
| realPluginName = self.host.get_real_plugin_name(self.fPluginId) | |||||
| labelURI = self.fPluginInfo['label'] | |||||
| strLoadState = "" | |||||
| programCount = self.host.get_program_count(self.fPluginId) | |||||
| if programCount > 0: | |||||
| strLoadState = '<div style="letter-spacing:1px"><br>'\ | |||||
| '<b>Note: </b>This plugin collected some presets for you.<br>'\ | |||||
| 'Use <i>Edit</i> tab, then <i>Load State</i> button.</div>' | |||||
| scene = QGraphicsScene(self) | |||||
| text = QGraphicsTextItem("",None) | |||||
| text.setTextInteractionFlags(Qt.TextSelectableByMouse) | |||||
| text.setTextWidth(600); | |||||
| # text.setFont(QFont("Arial, 16")) # NOTE: All Qt sizes are in Pt; real px~=4/3Pt. | |||||
| text.setHtml('<body>\ | |||||
| <h1>' + realPluginName + '</h1><br>'\ | |||||
| '<a href=' + labelURI + '>' + labelURI + '</a><br><br>'\ | |||||
| '<div style="line-height:1.5;">' + strDescr + '</div>' +\ | |||||
| strLoadState +\ | |||||
| '<body>'); | |||||
| scene.addItem(text) | |||||
| view = QGraphicsView(scene, self) | |||||
| self.ui.tabWidget.addTab(view, tabPageName) | |||||
| #------------------------------------------------------------------ | |||||
| def testTimer(self): | def testTimer(self): | ||||
| self.fIdleTimerId = self.startTimer(50) | self.fIdleTimerId = self.startTimer(50) | ||||
| @@ -33,7 +33,7 @@ class QToolButtonWithMouseTracking(QToolButton): | |||||
| QToolButton.leaveEvent(self, event) | QToolButton.leaveEvent(self, event) | ||||
| class CollapsibleBox(QFrame): | class CollapsibleBox(QFrame): | ||||
| def __init__(self, title, parent): | |||||
| def __init__(self, title, parent, startsExpanded = True): | |||||
| QFrame.__init__(self, parent) | QFrame.__init__(self, parent) | ||||
| self.setFrameShape(QFrame.StyledPanel) | self.setFrameShape(QFrame.StyledPanel) | ||||
| @@ -42,10 +42,10 @@ class CollapsibleBox(QFrame): | |||||
| self.toggle_button = QToolButtonWithMouseTracking(self) | self.toggle_button = QToolButtonWithMouseTracking(self) | ||||
| self.toggle_button.setText(title) | self.toggle_button.setText(title) | ||||
| self.toggle_button.setCheckable(True) | self.toggle_button.setCheckable(True) | ||||
| self.toggle_button.setChecked(True) | |||||
| self.toggle_button.setChecked(startsExpanded) | |||||
| self.toggle_button.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) | self.toggle_button.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) | ||||
| self.toggle_button.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) | self.toggle_button.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) | ||||
| self.toggle_button.setArrowType(Qt.DownArrow) | |||||
| # self.toggle_button.setArrowType(Qt.DownArrow) # Not deleted, just moved | |||||
| self.toggle_button.toggled.connect(self.toolButtonPressed) | self.toggle_button.toggled.connect(self.toolButtonPressed) | ||||
| self.content_area = QWidget(self) | self.content_area = QWidget(self) | ||||
| @@ -59,6 +59,8 @@ class CollapsibleBox(QFrame): | |||||
| lay.addWidget(self.toggle_button) | lay.addWidget(self.toggle_button) | ||||
| lay.addWidget(self.content_area) | lay.addWidget(self.content_area) | ||||
| self.toolButtonPressed(startsExpanded) # Set initial state | |||||
| @pyqtSlot(bool) | @pyqtSlot(bool) | ||||
| def toolButtonPressed(self, toggled): | def toolButtonPressed(self, toggled): | ||||
| self.content_area.setVisible(toggled) | self.content_area.setVisible(toggled) | ||||
| @@ -1,22 +1,26 @@ | |||||
| #!/usr/bin/env python3 | #!/usr/bin/env python3 | ||||
| # SPDX-FileCopyrightText: 2011-2024 Filipe Coelho <falktx@falktx.com> | |||||
| # SPDX-FileCopyrightText: 2011-2025 Filipe Coelho <falktx@falktx.com> | |||||
| # SPDX-License-Identifier: GPL-2.0-or-later | # SPDX-License-Identifier: GPL-2.0-or-later | ||||
| # --------------------------------------------------------------------------------------------------------------------- | # --------------------------------------------------------------------------------------------------------------------- | ||||
| # Imports (Global) | # Imports (Global) | ||||
| from math import isnan | |||||
| from math import isnan, log10 | |||||
| from qt_compat import qt_config | from qt_compat import qt_config | ||||
| if qt_config == 5: | if qt_config == 5: | ||||
| from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QPointF, QRectF | |||||
| from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QPoint, QPointF, QRectF, QEvent, QTimer | |||||
| from PyQt5.QtGui import QColor, QFont, QLinearGradient, QPainter | from PyQt5.QtGui import QColor, QFont, QLinearGradient, QPainter | ||||
| from PyQt5.QtWidgets import QDial | |||||
| from PyQt5.QtWidgets import QWidget, QToolTip, QInputDialog | |||||
| elif qt_config == 6: | elif qt_config == 6: | ||||
| from PyQt6.QtCore import pyqtSignal, pyqtSlot, Qt, QPointF, QRectF | |||||
| from PyQt6.QtCore import pyqtSignal, pyqtSlot, Qt, QPoint, QPointF, QRectF, QEvent, QTimer | |||||
| from PyQt6.QtGui import QColor, QFont, QLinearGradient, QPainter | from PyQt6.QtGui import QColor, QFont, QLinearGradient, QPainter | ||||
| from PyQt6.QtWidgets import QDial | |||||
| from PyQt6.QtWidgets import QWidget, QToolTip, QInputDialog | |||||
| from carla_shared import strLim | |||||
| from widgets.paramspinbox import CustomInputDialog | |||||
| from carla_backend import PARAMETER_NULL, PARAMETER_DRYWET, PARAMETER_VOLUME, PARAMETER_BALANCE_LEFT, PARAMETER_BALANCE_RIGHT, PARAMETER_PANNING | |||||
| # --------------------------------------------------------------------------------------------------------------------- | # --------------------------------------------------------------------------------------------------------------------- | ||||
| # Widget Class | # Widget Class | ||||
| @@ -25,7 +29,7 @@ elif qt_config == 6: | |||||
| #def updateSizes(self): | #def updateSizes(self): | ||||
| #def paintDial(self, painter): | #def paintDial(self, painter): | ||||
| class CommonDial(QDial): | |||||
| class CommonDial(QWidget): | |||||
| # enum CustomPaintMode | # enum CustomPaintMode | ||||
| CUSTOM_PAINT_MODE_NULL = 0 # default (NOTE: only this mode has label gradient) | CUSTOM_PAINT_MODE_NULL = 0 # default (NOTE: only this mode has label gradient) | ||||
| CUSTOM_PAINT_MODE_CARLA_WET = 1 # color blue-green gradient (reserved #3) | CUSTOM_PAINT_MODE_CARLA_WET = 1 # color blue-green gradient (reserved #3) | ||||
| @@ -33,9 +37,11 @@ class CommonDial(QDial): | |||||
| CUSTOM_PAINT_MODE_CARLA_L = 3 # color yellow (reserved #4) | CUSTOM_PAINT_MODE_CARLA_L = 3 # color yellow (reserved #4) | ||||
| CUSTOM_PAINT_MODE_CARLA_R = 4 # color yellow (reserved #4) | CUSTOM_PAINT_MODE_CARLA_R = 4 # color yellow (reserved #4) | ||||
| CUSTOM_PAINT_MODE_CARLA_PAN = 5 # color yellow (reserved #3) | CUSTOM_PAINT_MODE_CARLA_PAN = 5 # color yellow (reserved #3) | ||||
| CUSTOM_PAINT_MODE_COLOR = 6 # color, selectable (reserved #3) | |||||
| CUSTOM_PAINT_MODE_ZITA = 7 # custom zita knob (reserved #6) | |||||
| CUSTOM_PAINT_MODE_CARLA_FORTH = 6 # Experimental | |||||
| CUSTOM_PAINT_MODE_COLOR = 7 # May be deprecated (unless zynfx internal mode) | |||||
| CUSTOM_PAINT_MODE_NO_GRADIENT = 8 # skip label gradient | CUSTOM_PAINT_MODE_NO_GRADIENT = 8 # skip label gradient | ||||
| CUSTOM_PAINT_MODE_CARLA_WET_MINI = 9 # for compacted slot | |||||
| CUSTOM_PAINT_MODE_CARLA_VOL_MINI = 10 # for compacted slot | |||||
| # enum Orientation | # enum Orientation | ||||
| HORIZONTAL = 0 | HORIZONTAL = 0 | ||||
| @@ -51,16 +57,37 @@ class CommonDial(QDial): | |||||
| dragStateChanged = pyqtSignal(bool) | dragStateChanged = pyqtSignal(bool) | ||||
| realValueChanged = pyqtSignal(float) | realValueChanged = pyqtSignal(float) | ||||
| def __init__(self, parent, index): | |||||
| QDial.__init__(self, parent) | |||||
| def __init__(self, parent, index, precision, default, minimum, maximum, label, paintMode, colorHint, unit, skinStyle, whiteLabels, tweaks, isInteger, isButton, isOutput, isVuOutput, isVisible): | |||||
| QWidget.__init__(self, parent) | |||||
| self.fIndex = index | |||||
| self.fPrecision = precision | |||||
| self.fDefault = default | |||||
| self.fMinimum = minimum | |||||
| self.fMaximum = maximum | |||||
| self.fCustomPaintMode = paintMode | |||||
| self.fColorHint = colorHint | |||||
| self.fUnit = unit | |||||
| self.fSkinStyle = skinStyle | |||||
| self.fWhiteLabels = whiteLabels | |||||
| self.fTweaks = tweaks | |||||
| self.fIsInteger = isInteger | |||||
| self.fIsButton = isButton | |||||
| self.fIsOutput = isOutput | |||||
| self.fIsVuOutput = isVuOutput | |||||
| self.fIsVisible = isVisible | |||||
| self.fDialMode = self.MODE_LINEAR | self.fDialMode = self.MODE_LINEAR | ||||
| self.fMinimum = 0.0 | |||||
| self.fMaximum = 1.0 | |||||
| self.fLabel = label | |||||
| self.fLastLabel = "" | |||||
| self.fRealValue = 0.0 | self.fRealValue = 0.0 | ||||
| self.fPrecision = 10000 | |||||
| self.fIsInteger = False | |||||
| self.fLastValue = self.fDefault | |||||
| self.fScalePoints = [] | |||||
| self.fNumScalePoints = 0 | |||||
| self.fScalePointsPrefix = "" | |||||
| self.fScalePointsSuffix = "" | |||||
| self.fIsHovered = False | self.fIsHovered = False | ||||
| self.fIsPressed = False | self.fIsPressed = False | ||||
| @@ -69,9 +96,6 @@ class CommonDial(QDial): | |||||
| self.fLastDragPos = None | self.fLastDragPos = None | ||||
| self.fLastDragValue = 0.0 | self.fLastDragValue = 0.0 | ||||
| self.fIndex = index | |||||
| self.fLabel = "" | |||||
| self.fLabelPos = QPointF(0.0, 0.0) | self.fLabelPos = QPointF(0.0, 0.0) | ||||
| self.fLabelFont = QFont(self.font()) | self.fLabelFont = QFont(self.font()) | ||||
| self.fLabelFont.setPixelSize(8) | self.fLabelFont.setPixelSize(8) | ||||
| @@ -97,75 +121,79 @@ class CommonDial(QDial): | |||||
| self.fLabelGradientRect = QRectF(0.0, 0.0, 0.0, 0.0) | self.fLabelGradientRect = QRectF(0.0, 0.0, 0.0, 0.0) | ||||
| self.fCustomPaintMode = self.CUSTOM_PAINT_MODE_NULL | |||||
| self.fCustomPaintColor = QColor(0xff, 0xff, 0xff) | self.fCustomPaintColor = QColor(0xff, 0xff, 0xff) | ||||
| # Fake internal value, custom precision | |||||
| QDial.setMinimum(self, 0) | |||||
| QDial.setMaximum(self, self.fPrecision) | |||||
| QDial.setValue(self, 0) | |||||
| self.addContrast = int(bool(self.getTweak('HighContrast', 0))) | |||||
| self.colorFollow = bool(self.getTweak('ColorFollow', 0)) | |||||
| self.knobPusheable = bool(self.getTweak('WetVolPush', 0)) | |||||
| self.displayTooltip = bool(self.getTweak('Tooltips', 1)) | |||||
| self.valueChanged.connect(self.slot_valueChanged) | |||||
| # We have two group of knobs, non-repaintable (like in Edit dialog) and normal. | |||||
| # For non-repaintable, we init sizes/color once here; | |||||
| # for normals, it should be (re)inited separately: we do not init it here | |||||
| # to save CPU, some parameters are not known yet, repaint need anyway. | |||||
| if self.fColorHint == -1: | |||||
| self.updateSizes() | |||||
| self.update() | |||||
| # self.valueChanged.connect(self.slot_valueChanged) # FIXME | |||||
| def forceWhiteLabelGradientText(self): | def forceWhiteLabelGradientText(self): | ||||
| self.fLabelGradientColor1 = QColor(0, 0, 0, 255) | self.fLabelGradientColor1 = QColor(0, 0, 0, 255) | ||||
| self.fLabelGradientColor2 = QColor(0, 0, 0, 0) | self.fLabelGradientColor2 = QColor(0, 0, 0, 0) | ||||
| self.fLabelGradientColorT = [Qt.white, Qt.darkGray] | self.fLabelGradientColorT = [Qt.white, Qt.darkGray] | ||||
| def setLabelColor(self, enabled, disabled): | |||||
| self.fLabelGradientColor1 = QColor(0, 0, 0, 255) | |||||
| self.fLabelGradientColor2 = QColor(0, 0, 0, 0) | |||||
| self.fLabelGradientColorT = [enabled, disabled] | |||||
| # def setLabelColor(self, enabled, disabled): | |||||
| # self.fLabelGradientColor1 = QColor(0, 0, 0, 255) | |||||
| # self.fLabelGradientColor2 = QColor(0, 0, 0, 0) | |||||
| # self.fLabelGradientColorT = [enabled, disabled] | |||||
| def getIndex(self): | def getIndex(self): | ||||
| return self.fIndex | return self.fIndex | ||||
| def setIndex(self, index): | |||||
| self.fIndex = index | |||||
| def setPrecision(self, value, isInteger): | |||||
| self.fPrecision = value | |||||
| self.fIsInteger = isInteger | |||||
| QDial.setMaximum(self, int(value)) | |||||
| def setMinimum(self, value): | |||||
| self.fMinimum = value | |||||
| def setMaximum(self, value): | |||||
| self.fMaximum = value | |||||
| def rvalue(self): | def rvalue(self): | ||||
| return self.fRealValue | return self.fRealValue | ||||
| def pushLabel(self, label): | |||||
| if self.fLastLabel == "": | |||||
| self.fLastLabel = self.fLabel | |||||
| self.fLabel = label | |||||
| self.updateSizes() | |||||
| self.update() | |||||
| def popLabel(self): | |||||
| if not (self.fLastLabel == ""): | |||||
| self.fLabel = self.fLastLabel | |||||
| self.fLastLabel = "" | |||||
| self.updateSizes() | |||||
| self.update() | |||||
| def setScalePPS(self, scalePoints, prefix, suffix): | |||||
| self.fScalePoints = scalePoints | |||||
| self.fNumScalePoints = len(self.fScalePoints) | |||||
| self.fScalePointsPrefix = prefix | |||||
| self.fScalePointsSuffix = suffix | |||||
| def setValue(self, value, emitSignal=False): | def setValue(self, value, emitSignal=False): | ||||
| if self.fRealValue == value or isnan(value): | if self.fRealValue == value or isnan(value): | ||||
| return | return | ||||
| if value <= self.fMinimum: | |||||
| qtValue = 0 | |||||
| if (not self.fIsOutput) and value <= self.fMinimum: | |||||
| self.fRealValue = self.fMinimum | self.fRealValue = self.fMinimum | ||||
| elif value >= self.fMaximum: | |||||
| qtValue = int(self.fPrecision) | |||||
| elif (not self.fIsOutput) and value >= self.fMaximum: | |||||
| self.fRealValue = self.fMaximum | self.fRealValue = self.fMaximum | ||||
| elif self.fIsInteger or (abs(value - int(value)) < 1e-8): # tiny "notch" | |||||
| self.fRealValue = round(value) | |||||
| else: | else: | ||||
| qtValue = round(float(value - self.fMinimum) / float(self.fMaximum - self.fMinimum) * self.fPrecision) | |||||
| self.fRealValue = value | self.fRealValue = value | ||||
| # Block change signal, we'll handle it ourselves | |||||
| self.blockSignals(True) | |||||
| QDial.setValue(self, qtValue) | |||||
| self.blockSignals(False) | |||||
| if emitSignal: | if emitSignal: | ||||
| self.realValueChanged.emit(self.fRealValue) | self.realValueChanged.emit(self.fRealValue) | ||||
| def setCustomPaintMode(self, paintMode): | |||||
| if self.fCustomPaintMode == paintMode: | |||||
| return | |||||
| self.fCustomPaintMode = paintMode | |||||
| self.update() | self.update() | ||||
| def setCustomPaintColor(self, color): | def setCustomPaintColor(self, color): | ||||
| @@ -173,76 +201,262 @@ class CommonDial(QDial): | |||||
| return | return | ||||
| self.fCustomPaintColor = color | self.fCustomPaintColor = color | ||||
| self.updateSizes() | |||||
| self.update() | self.update() | ||||
| def setLabel(self, label): | |||||
| if self.fLabel == label: | |||||
| return | |||||
| def getTweak(self, tweakName, default): | |||||
| return self.fTweaks.get(self.fSkinStyle + tweakName, self.fTweaks.get(tweakName, default)) | |||||
| self.fLabel = label | |||||
| self.updateSizes() | |||||
| self.update() | |||||
| def getIsVisible(self): | |||||
| # print (self.fIsVisible) | |||||
| return self.fIsVisible | |||||
| @pyqtSlot(int) | @pyqtSlot(int) | ||||
| def slot_valueChanged(self, value): | def slot_valueChanged(self, value): | ||||
| self.fRealValue = float(value)/self.fPrecision * (self.fMaximum - self.fMinimum) + self.fMinimum | self.fRealValue = float(value)/self.fPrecision * (self.fMaximum - self.fMinimum) + self.fMinimum | ||||
| self.realValueChanged.emit(self.fRealValue) | self.realValueChanged.emit(self.fRealValue) | ||||
| # jpka: TODO should be replaced by common dialog, but | |||||
| # PluginEdit.slot_knobCustomMenu(...) - not found, import not work. | |||||
| # So this is copy w/o access to 'step's. | |||||
| def knobCustomInputDialog(self): | |||||
| if self.fIndex < PARAMETER_NULL: | |||||
| percent = 100.0 | |||||
| else: | |||||
| percent = 1 | |||||
| if self.fIsInteger: | |||||
| step = max(1, int((self.fMaximum - self.fMinimum)/100)) | |||||
| stepSmall = max(1, int(step/10)) | |||||
| else: | |||||
| step = 10 ** (round(log10((self.fMaximum - self.fMinimum) * percent))-2) | |||||
| stepSmall = step / 100 | |||||
| dialog = CustomInputDialog(self, self.fLabel, self.fRealValue * percent, self.fMinimum * percent, self.fMaximum * percent, step, stepSmall, self.fScalePoints, "", "", self.fUnit) | |||||
| if not dialog.exec_(): | |||||
| return | |||||
| self.setValue(dialog.returnValue() / percent, True) | |||||
| def enterEvent(self, event): | def enterEvent(self, event): | ||||
| self.setFocus() | |||||
| self.fIsHovered = True | self.fIsHovered = True | ||||
| if self.fHoverStep == self.HOVER_MIN: | if self.fHoverStep == self.HOVER_MIN: | ||||
| self.fHoverStep = self.HOVER_MIN + 1 | self.fHoverStep = self.HOVER_MIN + 1 | ||||
| QDial.enterEvent(self, event) | |||||
| self.update() | |||||
| def leaveEvent(self, event): | def leaveEvent(self, event): | ||||
| self.fIsHovered = False | self.fIsHovered = False | ||||
| if self.fHoverStep == self.HOVER_MAX: | if self.fHoverStep == self.HOVER_MAX: | ||||
| self.fHoverStep = self.HOVER_MAX - 1 | self.fHoverStep = self.HOVER_MAX - 1 | ||||
| QDial.leaveEvent(self, event) | |||||
| self.update() | |||||
| def nextScalePoint(self): | |||||
| for i in range(self.fNumScalePoints): | |||||
| value = self.fScalePoints[i]['value'] | |||||
| if value > self.fRealValue: | |||||
| self.setValue(value, True) | |||||
| return | |||||
| self.setValue(self.fScalePoints[0]['value'], True) | |||||
| def mousePressEvent(self, event): | def mousePressEvent(self, event): | ||||
| if self.fDialMode == self.MODE_DEFAULT: | |||||
| QDial.mousePressEvent(self, event) | |||||
| if self.fDialMode == self.MODE_DEFAULT or self.fIsOutput: | |||||
| return | return | ||||
| if event.button() == Qt.LeftButton: | if event.button() == Qt.LeftButton: | ||||
| self.fIsPressed = True | |||||
| self.fLastDragPos = event.pos() | |||||
| self.fLastDragValue = self.fRealValue | |||||
| self.dragStateChanged.emit(True) | |||||
| # if self.fNumScalePoints: | |||||
| # self.nextScalePoint() | |||||
| # | |||||
| if self.fIsButton: | |||||
| value = int(self.fRealValue) + 1; | |||||
| if (value > self.fMaximum): | |||||
| value = 0 | |||||
| self.setValue(value, True) | |||||
| else: | |||||
| self.fIsPressed = True | |||||
| self.fLastDragPos = event.pos() | |||||
| self.fLastDragValue = self.fRealValue | |||||
| self.dragStateChanged.emit(True) | |||||
| elif event.button() == Qt.MiddleButton: | |||||
| if self.fIsOutput: | |||||
| return | |||||
| self.setValue(self.fDefault, True) | |||||
| def mouseDoubleClickEvent(self, event): | |||||
| if self.knobPusheable and self.fIndex in (PARAMETER_DRYWET, PARAMETER_VOLUME, PARAMETER_PANNING): # -3, -4, -7 | |||||
| return # Mutex with special Single Click | |||||
| if event.button() == Qt.LeftButton: | |||||
| if self.fIsButton: | |||||
| value = int(self.fRealValue) + 1; | |||||
| if (value > self.fMaximum): | |||||
| value = 0 | |||||
| self.setValue(value, True) | |||||
| else: | |||||
| if self.fIsOutput: | |||||
| return | |||||
| self.knobCustomInputDialog() | |||||
| def mouseMoveEvent(self, event): | def mouseMoveEvent(self, event): | ||||
| if self.fDialMode == self.MODE_DEFAULT: | |||||
| QDial.mouseMoveEvent(self, event) | |||||
| if self.fDialMode == self.MODE_DEFAULT or self.fIsOutput: | |||||
| return | return | ||||
| if not self.fIsPressed: | if not self.fIsPressed: | ||||
| return | return | ||||
| diff = (self.fMaximum - self.fMinimum) / 4.0 | |||||
| pos = event.pos() | |||||
| dx = diff * float(pos.x() - self.fLastDragPos.x()) / self.width() | |||||
| dy = diff * float(pos.y() - self.fLastDragPos.y()) / self.height() | |||||
| value = self.fLastDragValue + dx - dy | |||||
| pos = event.pos() | |||||
| delta = (float(pos.x() - self.fLastDragPos.x()) - float(pos.y() - self.fLastDragPos.y())) / 10 | |||||
| if value < self.fMinimum: | |||||
| value = self.fMinimum | |||||
| elif value > self.fMaximum: | |||||
| value = self.fMaximum | |||||
| elif self.fIsInteger: | |||||
| value = float(round(value)) | |||||
| mod = event.modifiers() | |||||
| self.applyDelta(mod, delta, True) | |||||
| self.setValue(value, True) | |||||
| def mouseReleaseEvent(self, event): | def mouseReleaseEvent(self, event): | ||||
| if self.fDialMode == self.MODE_DEFAULT: | |||||
| QDial.mouseReleaseEvent(self, event) | |||||
| if self.fDialMode == self.MODE_DEFAULT or self.fIsOutput: | |||||
| return | return | ||||
| if self.fIsPressed: | if self.fIsPressed: | ||||
| self.fIsPressed = False | self.fIsPressed = False | ||||
| self.dragStateChanged.emit(False) | self.dragStateChanged.emit(False) | ||||
| if event.button() == Qt.LeftButton: | |||||
| if event.pos() == self.fLastDragPos: | |||||
| if self.fNumScalePoints: | |||||
| self.nextScalePoint() | |||||
| else: | |||||
| self.knobPush() | |||||
| # NOTE: fLastLabel state managed @ scalabledial | |||||
| def knobPush(self): | |||||
| if self.knobPusheable and self.fIndex in (PARAMETER_DRYWET, PARAMETER_VOLUME, PARAMETER_PANNING): # -3, -4, -7 | |||||
| if self.fLastLabel == "": # push value | |||||
| self.fLastValue = self.fRealValue | |||||
| self.setValue(0, True) # Thru or Mute | |||||
| else: # pop value | |||||
| self.setValue(self.fLastValue, True) | |||||
| def applyDelta(self, mod, delta, anchor = False): | |||||
| if self.fIsOutput: | |||||
| return | |||||
| if self.fIsButton: | |||||
| self.setValue(self.fRealValue + delta, True) | |||||
| return | |||||
| if self.fIsInteger: # 4 to 50 ticks per revolution | |||||
| if (mod & Qt.ShiftModifier): | |||||
| delta = delta * 5 | |||||
| elif (mod & Qt.ControlModifier): | |||||
| delta = delta / min(int((self.fMaximum-self.fMinimum)/self.fPrecision), 5) | |||||
| else: # Floats are 250 to 500 ticks per revolution | |||||
| # jpka: 1. Should i use these steps? | |||||
| # 2. And what do i do when i TODO add MODE_LOG along with MODE_LINEAR? | |||||
| # 3. And they're too small for large ints like in TAP Reverb, and strange for scalepoints. | |||||
| # paramRanges = self.host.get_parameter_ranges(self.fPluginId, i) | |||||
| # paramRanges['step'], paramRanges['stepSmall'], paramRanges['stepLarge'] | |||||
| if (mod & Qt.ControlModifier) and (mod & Qt.ShiftModifier): | |||||
| delta = delta * 2/5 | |||||
| elif (mod & Qt.ControlModifier): | |||||
| delta = delta * 2 | |||||
| elif (mod & Qt.ShiftModifier): | |||||
| delta = delta * 50 | |||||
| else: | |||||
| delta = delta * 10 | |||||
| difference = float(self.fMaximum-self.fMinimum) * float(delta) / float(self.fPrecision) | |||||
| if anchor: | |||||
| self.setValue((self.fLastDragValue + difference), True) | |||||
| else: | |||||
| self.setValue((self.fRealValue + difference), True) | |||||
| return | |||||
| def wheelEvent(self, event): | |||||
| if self.fIsOutput: | |||||
| return | |||||
| direction = event.angleDelta().y() | |||||
| if direction < 0: | |||||
| delta = -1.0 | |||||
| elif direction > 0: | |||||
| delta = 1.0 | |||||
| else: | |||||
| return | |||||
| mod = event.modifiers() | |||||
| self.applyDelta(mod, delta) | |||||
| return | |||||
| def keyPressEvent(self, event): | |||||
| if self.fIsOutput: | |||||
| return | |||||
| key = event.key() | |||||
| mod = event.modifiers() | |||||
| modsNone = not ((mod & Qt.ShiftModifier) | (mod & Qt.ControlModifier) | (mod & Qt.AltModifier)) | |||||
| if modsNone: | |||||
| match key: | |||||
| case Qt.Key_Space | Qt.Key_Enter | Qt.Key_Return : | |||||
| if self.fIsButton: | |||||
| value = int(self.fRealValue) + 1 | |||||
| if (value > self.fMaximum): | |||||
| value = 0 | |||||
| self.setValue(value, True) | |||||
| elif not key == Qt.Key_Space: | |||||
| self.knobCustomInputDialog() | |||||
| else: | |||||
| if self.fNumScalePoints: | |||||
| self.nextScalePoint() | |||||
| else: | |||||
| self.knobPush() | |||||
| case Qt.Key_E: | |||||
| self.knobCustomInputDialog() | |||||
| case key if Qt.Key_0 <= key <= Qt.Key_9: | |||||
| if self.fIsInteger and (self.fMinimum == 0) and (self.fMaximum <= 10): | |||||
| self.setValue(key-Qt.Key_0, True) | |||||
| else: | |||||
| self.setValue(self.fMinimum + float(self.fMaximum-self.fMinimum)/10.0*(key-Qt.Key_0), True) | |||||
| case Qt.Key_Home: # NOTE: interferes with Canvas control hotkey | |||||
| self.setValue(self.fMinimum, True) | |||||
| case Qt.Key_End: | |||||
| self.setValue(self.fMaximum, True) | |||||
| case Qt.Key_D: | |||||
| self.setValue(self.fDefault, True) | |||||
| case Qt.Key_R: | |||||
| self.setValue(self.fDefault, True) | |||||
| match key: | |||||
| case Qt.Key_PageDown: | |||||
| self.applyDelta(mod, -1) | |||||
| case Qt.Key_PageUp: | |||||
| self.applyDelta(mod, 1) | |||||
| return | |||||
| def paintEvent(self, event): | def paintEvent(self, event): | ||||
| painter = QPainter(self) | painter = QPainter(self) | ||||
| event.accept() | event.accept() | ||||
| @@ -250,22 +464,118 @@ class CommonDial(QDial): | |||||
| painter.save() | painter.save() | ||||
| painter.setRenderHint(QPainter.Antialiasing, True) | painter.setRenderHint(QPainter.Antialiasing, True) | ||||
| enabled = int(bool(self.isEnabled())) | |||||
| if enabled: | |||||
| self.setContextMenuPolicy(Qt.CustomContextMenu) | |||||
| else: | |||||
| self.setContextMenuPolicy(Qt.NoContextMenu) | |||||
| if self.fLabel: | if self.fLabel: | ||||
| if self.fCustomPaintMode == self.CUSTOM_PAINT_MODE_NULL: | |||||
| painter.setPen(self.fLabelGradientColor2) | |||||
| painter.setBrush(self.fLabelGradient) | |||||
| painter.drawRect(self.fLabelGradientRect) | |||||
| # if self.fCustomPaintMode == self.CUSTOM_PAINT_MODE_NULL: | |||||
| # painter.setPen(self.fLabelGradientColor2) | |||||
| # painter.setBrush(self.fLabelGradient) | |||||
| # # painter.drawRect(self.fLabelGradientRect) FIXME restore gradients. | |||||
| luma = int(bool(self.fWhiteLabels)) - 0.5 | |||||
| if enabled: | |||||
| L = (luma * (1.6 + self.addContrast * 0.4)) / 2 + 0.5 | |||||
| else: | |||||
| L = (luma * (0.2 + self.addContrast * 0.2)) / 2 + 0.5 | |||||
| painter.setFont(self.fLabelFont) | painter.setFont(self.fLabelFont) | ||||
| painter.setPen(self.fLabelGradientColorT[0 if self.isEnabled() else 1]) | |||||
| # painter.setPen(self.fLabelGradientColorT[0 if self.fIsEnabled() else 1]) | |||||
| painter.setPen(QColor.fromHslF(0, 0, L, 1)) | |||||
| painter.drawText(self.fLabelPos, self.fLabel) | painter.drawText(self.fLabelPos, self.fLabel) | ||||
| self.paintDial(painter) | |||||
| X = self.fWidth / 2 | |||||
| Y = self.fHeight / 2 | |||||
| S = enabled * 0.9 # saturation | |||||
| E = enabled * self.fHoverStep / 40 # enlight | |||||
| L = 0.6 + E | |||||
| if self.addContrast: | |||||
| L = min(L + 0.3, 1) # luma | |||||
| normValue = float(self.fRealValue - self.fMinimum) / float(self.fMaximum - self.fMinimum) | |||||
| # Work In Progress FIXME | |||||
| H=0 | |||||
| if self.fIsOutput: | |||||
| if self.fIsButton: | |||||
| # self.paintLed (painter, X, Y, H, S, L, E, normValue) | |||||
| self.paintDisplay(painter, X, Y, H, S, L, E, normValue, enabled) | |||||
| else: | |||||
| self.paintDisplay(painter, X, Y, H, S, L, E, normValue, enabled) | |||||
| else: | |||||
| if self.fIsButton: | |||||
| self.paintButton (painter, X, Y, H, S, L, E, normValue, enabled) | |||||
| else: | |||||
| self.paintDial (painter, X, Y, H, S, L, E, normValue, enabled) | |||||
| # Display tooltip, above the knob (OS-independent, unlike of mouse tooltip). | |||||
| # Note, update/redraw Qt's tooltip eats much more CPU than expected, | |||||
| # so we have tweak for turn it off. See also #1934. | |||||
| if self.fHoverStep == self.HOVER_MAX and self.displayTooltip: | |||||
| # First, we need to find exact or nearest match (index from value). | |||||
| # It is also tests if we have scale points at all. | |||||
| num = -1 | |||||
| for i in range(self.fNumScalePoints): | |||||
| scaleValue = self.fScalePoints[i]['value'] | |||||
| if i == 0: | |||||
| finalValue = scaleValue | |||||
| num = 0 | |||||
| else: | |||||
| srange2 = abs(self.fRealValue - finalValue) | |||||
| srange1 = abs(self.fRealValue - scaleValue) | |||||
| if srange2 > srange1: | |||||
| finalValue = scaleValue | |||||
| num = i | |||||
| if (srange1 == 0): # Exact match, save some CPU. | |||||
| break | |||||
| tip = "" | |||||
| if (num >= 0): # Scalepoints are used | |||||
| tip = str(self.fScalePoints[num]['label']) | |||||
| if not self.fIsButton: | |||||
| tip = self.fScalePointsPrefix + \ | |||||
| strLim(self.fScalePoints[num]['value']) + \ | |||||
| self.fScalePointsSuffix + ": " + tip | |||||
| # ? We most probably not need tooltip for button, if it is not scalepoint. | |||||
| # elif not self.fIsButton: | |||||
| else: | |||||
| if self.fRealValue == 0 and self.fIndex == PARAMETER_DRYWET: #-3,-4,-7,-9 | |||||
| tip = "THRU" | |||||
| elif self.fRealValue == 0 and self.fIndex == PARAMETER_VOLUME: | |||||
| tip = "MUTE" | |||||
| elif self.fRealValue == 0 and self.fIndex == PARAMETER_PANNING: | |||||
| tip = "Center" | |||||
| else: | |||||
| if self.fIndex < PARAMETER_NULL: | |||||
| percent = 100.0 | |||||
| else: | |||||
| percent = 1 | |||||
| tip = (strLim(self.fRealValue * percent) + " " + self.fUnit).strip() | |||||
| if self.fIsOutput: | |||||
| tip = tip + " [" + strLim(self.fMinimum * percent) + "..." + \ | |||||
| strLim(self.fMaximum * percent) + "]" | |||||
| # Wrong vert. position for Calf: | |||||
| # QToolTip.showText(self.mapToGlobal(QPoint(0, 0-self.geometry().height())), tip) | |||||
| # FIXME Still wrong vert. position for QT_SCALE_FACTOR=2. | |||||
| QToolTip.showText(self.mapToGlobal(QPoint(0, 0-45)), tip) | |||||
| else: | |||||
| QToolTip.hideText() | |||||
| if enabled: | |||||
| if self.HOVER_MIN < self.fHoverStep < self.HOVER_MAX: | |||||
| self.fHoverStep += 1 if self.fIsHovered else -1 | |||||
| QTimer.singleShot(20, self.update) | |||||
| painter.restore() | painter.restore() | ||||
| def resizeEvent(self, event): | |||||
| QDial.resizeEvent(self, event) | |||||
| self.updateSizes() | |||||
| # def resizeEvent(self, event): | |||||
| # QWidget.resizeEvent(self, event) | |||||
| # self.updateSizes() | |||||
| # --------------------------------------------------------------------------------------------------------------------- | # --------------------------------------------------------------------------------------------------------------------- | ||||
| @@ -35,6 +35,7 @@ class DigitalPeakMeter(QWidget): | |||||
| STYLE_OPENAV = 2 | STYLE_OPENAV = 2 | ||||
| STYLE_RNCBC = 3 | STYLE_RNCBC = 3 | ||||
| STYLE_CALF = 4 | STYLE_CALF = 4 | ||||
| STYLE_TUBE = 5 | |||||
| # ----------------------------------------------------------------------------------------------------------------- | # ----------------------------------------------------------------------------------------------------------------- | ||||
| @@ -153,7 +154,7 @@ class DigitalPeakMeter(QWidget): | |||||
| if self.fMeterStyle == style: | if self.fMeterStyle == style: | ||||
| return | return | ||||
| if style not in (self.STYLE_DEFAULT, self.STYLE_OPENAV, self.STYLE_RNCBC, self.STYLE_CALF): | |||||
| if style not in (self.STYLE_DEFAULT, self.STYLE_OPENAV, self.STYLE_RNCBC, self.STYLE_CALF, self.STYLE_TUBE): | |||||
| qCritical(f"DigitalPeakMeter::setMeterStyle({style}) - invalid style") | qCritical(f"DigitalPeakMeter::setMeterStyle({style}) - invalid style") | ||||
| return | return | ||||
| @@ -163,7 +164,7 @@ class DigitalPeakMeter(QWidget): | |||||
| self.fMeterBackground = QColor("#1A1A1A") | self.fMeterBackground = QColor("#1A1A1A") | ||||
| elif style == self.STYLE_RNCBC: | elif style == self.STYLE_RNCBC: | ||||
| self.fMeterBackground = QColor("#070707") | self.fMeterBackground = QColor("#070707") | ||||
| elif style == self.STYLE_CALF: | |||||
| elif style in (self.STYLE_CALF, self.STYLE_TUBE): | |||||
| self.fMeterBackground = QColor("#000") | self.fMeterBackground = QColor("#000") | ||||
| if style == self.STYLE_CALF: | if style == self.STYLE_CALF: | ||||
| @@ -215,23 +216,36 @@ class DigitalPeakMeter(QWidget): | |||||
| i = meter - 1 | i = meter - 1 | ||||
| if level < 0.001: | |||||
| level = 0.0 | |||||
| elif level > 0.999: | |||||
| level = 1.0 | |||||
| if self.fSmoothMultiplier > 0 and not forced: | if self.fSmoothMultiplier > 0 and not forced: | ||||
| level = ( | level = ( | ||||
| (self.fLastChannelData[i] * float(self.fSmoothMultiplier) + level) | (self.fLastChannelData[i] * float(self.fSmoothMultiplier) + level) | ||||
| / float(self.fSmoothMultiplier + 1) | / float(self.fSmoothMultiplier + 1) | ||||
| ) | ) | ||||
| if level < 0.001: | |||||
| level = 0.0 | |||||
| elif level > 0.999: | |||||
| level = 1.0 | |||||
| self.fLastChannelData[i] = level | |||||
| # Discretize scale: for 10 points, first will lit at 5%, | |||||
| # then 15%, and last at 95% of normalized value. | |||||
| # We also win some CPU when not redraw at small changes. | |||||
| if (self.fMeterStyle == self.STYLE_TUBE) and (level > 0.0) and (level < 1.0): | |||||
| points = 20 | |||||
| # Transform to Sq Root domain: our meters have Sqrt dynamic compression; | |||||
| # Discretize: | |||||
| level = int(sqrt(level) * points + 0.5) / points | |||||
| # Transform back from Square Root domain: | |||||
| level = level * level | |||||
| if self.fChannelData[i] != level: | if self.fChannelData[i] != level: | ||||
| self.fChannelData[i] = level | self.fChannelData[i] = level | ||||
| self.update() | self.update() | ||||
| self.fLastChannelData[i] = level | |||||
| # ----------------------------------------------------------------------------------------------------------------- | # ----------------------------------------------------------------------------------------------------------------- | ||||
| def updateGrandient(self): | def updateGrandient(self): | ||||
| @@ -284,6 +298,14 @@ class DigitalPeakMeter(QWidget): | |||||
| self.fMeterGradient.setColorAt(0.0, self.fMeterColorBase) | self.fMeterGradient.setColorAt(0.0, self.fMeterColorBase) | ||||
| self.fMeterGradient.setColorAt(1.0, self.fMeterColorBase) | self.fMeterGradient.setColorAt(1.0, self.fMeterColorBase) | ||||
| elif self.fMeterStyle == self.STYLE_TUBE: | |||||
| color = QColor.fromHslF(0.9, 1, 0.6, 1) # Tuneon filled w/ neon + agron | |||||
| points = 20 | |||||
| for i in range(points + 1): | |||||
| self.fMeterGradient.setColorAt(((i-0.3)/points % 1.0), color) | |||||
| self.fMeterGradient.setColorAt(( i /points ), Qt.black) | |||||
| self.fMeterGradient.setColorAt(((i+0.3)/points % 1.0), color) | |||||
| self.updateGrandientFinalStop() | self.updateGrandientFinalStop() | ||||
| def updateGrandientFinalStop(self): | def updateGrandientFinalStop(self): | ||||
| @@ -360,6 +382,13 @@ class DigitalPeakMeter(QWidget): | |||||
| meterPad += 2 | meterPad += 2 | ||||
| meterSize -= 2 | meterSize -= 2 | ||||
| elif self.fMeterStyle == self.STYLE_TUBE: | |||||
| painter.setPen(QPen(Qt.NoPen)) | |||||
| painter.setBrush(self.fMeterGradient) | |||||
| meterPos += 3 | |||||
| meterPad += 6 | |||||
| meterSize -= 6 | |||||
| else: | else: | ||||
| painter.setPen(QPen(self.fMeterBackground, 0)) | painter.setPen(QPen(self.fMeterBackground, 0)) | ||||
| painter.setBrush(self.fMeterGradient) | painter.setBrush(self.fMeterGradient) | ||||
| @@ -25,7 +25,7 @@ elif qt_config == 6: | |||||
| import ui_inputdialog_value | import ui_inputdialog_value | ||||
| from carla_backend import CARLA_OS_MAC | from carla_backend import CARLA_OS_MAC | ||||
| from carla_shared import countDecimalPoints | |||||
| from carla_shared import countDecimalPoints, getPrefixSuffix, strLim | |||||
| # ------------------------------------------------------------------------------------------------------------ | # ------------------------------------------------------------------------------------------------------------ | ||||
| # Get a fixed value within min/max bounds | # Get a fixed value within min/max bounds | ||||
| @@ -46,13 +46,21 @@ def geFixedValue(name, value, minimum, maximum): | |||||
| # Custom InputDialog with Scale Points support | # Custom InputDialog with Scale Points support | ||||
| class CustomInputDialog(QDialog): | class CustomInputDialog(QDialog): | ||||
| def __init__(self, parent, label, current, minimum, maximum, step, stepSmall, scalePoints, prefix, suffix): | |||||
| def __init__(self, parent, label, current, minimum, maximum, step, stepSmall, scalePoints, prefix, suffix, unit=""): | |||||
| QDialog.__init__(self, parent) | QDialog.__init__(self, parent) | ||||
| self.ui = ui_inputdialog_value.Ui_Dialog() | self.ui = ui_inputdialog_value.Ui_Dialog() | ||||
| self.ui.setupUi(self) | self.ui.setupUi(self) | ||||
| decimals = countDecimalPoints(step, stepSmall) | |||||
| self.ui.label.setText(label) | |||||
| if not (unit == ""): | |||||
| prefix, suffix = getPrefixSuffix(unit) | |||||
| if unit == "%": | |||||
| decimals = 1 | |||||
| else: | |||||
| decimals = countDecimalPoints(step, stepSmall) | |||||
| # self.ui.label.setText(label + " [" + strRound(self, minimum, decimals) + "..." + strRound(self, maximum, decimals) + "]") | |||||
| self.ui.label.setText(label + " [" + strLim(minimum) + "..." + strLim(maximum) + "]") | |||||
| self.ui.doubleSpinBox.setDecimals(decimals) | self.ui.doubleSpinBox.setDecimals(decimals) | ||||
| self.ui.doubleSpinBox.setRange(minimum, maximum) | self.ui.doubleSpinBox.setRange(minimum, maximum) | ||||
| self.ui.doubleSpinBox.setSingleStep(step) | self.ui.doubleSpinBox.setSingleStep(step) | ||||
| @@ -361,14 +369,7 @@ class ParamSpinBox(QAbstractSpinBox): | |||||
| self.fStepLarge = value | self.fStepLarge = value | ||||
| def setLabel(self, label): | def setLabel(self, label): | ||||
| prefix = "" | |||||
| suffix = label.strip() | |||||
| if suffix == "(coef)": | |||||
| prefix = "* " | |||||
| suffix = "" | |||||
| else: | |||||
| suffix = " " + suffix | |||||
| prefix, suffix = getPrefixSuffix(label) | |||||
| self.fLabelPrefix = prefix | self.fLabelPrefix = prefix | ||||
| self.fLabelSuffix = suffix | self.fLabelSuffix = suffix | ||||
| @@ -533,16 +534,16 @@ class ParamSpinBox(QAbstractSpinBox): | |||||
| pass | pass | ||||
| menu = QMenu(self) | menu = QMenu(self) | ||||
| actReset = menu.addAction(self.tr("Reset (%f)" % self.fDefault)) | |||||
| actReset = menu.addAction(self.tr("Reset (" + strLim(self.fDefault) + ")")) | |||||
| actRandom = menu.addAction(self.tr("Random")) | actRandom = menu.addAction(self.tr("Random")) | ||||
| menu.addSeparator() | menu.addSeparator() | ||||
| actCopy = menu.addAction(self.tr("Copy (%f)" % self.fValue)) | |||||
| actCopy = menu.addAction(self.tr("Copy (" + strLim(self.fValue) + ")")) | |||||
| if pasteValue is None: | if pasteValue is None: | ||||
| actPaste = menu.addAction(self.tr("Paste")) | actPaste = menu.addAction(self.tr("Paste")) | ||||
| actPaste.setEnabled(False) | actPaste.setEnabled(False) | ||||
| else: | else: | ||||
| actPaste = menu.addAction(self.tr("Paste (%f)" % pasteValue)) | |||||
| actPaste = menu.addAction(self.tr("Paste (" + strLim(pasteValue) + ")")) | |||||
| menu.addSeparator() | menu.addSeparator() | ||||
| @@ -13,11 +13,11 @@ import os | |||||
| from qt_compat import qt_config | from qt_compat import qt_config | ||||
| if qt_config == 5: | if qt_config == 5: | ||||
| from PyQt5.QtCore import Qt, QSize, QRect, QEvent | |||||
| from PyQt5.QtCore import QT_VERSION, Qt, QSize, QRect, QEvent | |||||
| from PyQt5.QtGui import QColor, QPainter, QPixmap | from PyQt5.QtGui import QColor, QPainter, QPixmap | ||||
| from PyQt5.QtWidgets import QAbstractItemView, QListWidget, QListWidgetItem, QMessageBox | from PyQt5.QtWidgets import QAbstractItemView, QListWidget, QListWidgetItem, QMessageBox | ||||
| elif qt_config == 6: | elif qt_config == 6: | ||||
| from PyQt6.QtCore import Qt, QSize, QRect, QEvent | |||||
| from PyQt6.QtCore import QT_VERSION, Qt, QSize, QRect, QEvent, QPoint # QPoint is for Qt6 only. | |||||
| from PyQt6.QtGui import QColor, QPainter, QPixmap | from PyQt6.QtGui import QColor, QPainter, QPixmap | ||||
| from PyQt6.QtWidgets import QAbstractItemView, QListWidget, QListWidgetItem, QMessageBox | from PyQt6.QtWidgets import QAbstractItemView, QListWidget, QListWidgetItem, QMessageBox | ||||
| @@ -291,7 +291,10 @@ class RackListWidget(QListWidget): | |||||
| event.acceptProposedAction() | event.acceptProposedAction() | ||||
| tryItem = self.itemAt(event.pos()) | |||||
| if QT_VERSION < 0x60000: | |||||
| tryItem = self.itemAt(event.pos()) | |||||
| else: | |||||
| tryItem = self.itemAt(QPoint(int(event.position().x()), int(event.position().y()))) | |||||
| if tryItem is not None: | if tryItem is not None: | ||||
| self.setCurrentRow(tryItem.getPluginId()) | self.setCurrentRow(tryItem.getPluginId()) | ||||
| @@ -318,7 +321,10 @@ class RackListWidget(QListWidget): | |||||
| if not urls: | if not urls: | ||||
| return | return | ||||
| tryItem = self.itemAt(event.pos()) | |||||
| if QT_VERSION < 0x60000: | |||||
| tryItem = self.itemAt(event.pos()) | |||||
| else: | |||||
| tryItem = self.itemAt(QPoint(int(event.position().x()), int(event.position().y()))) | |||||
| if tryItem is not None: | if tryItem is not None: | ||||
| pluginId = tryItem.getPluginId() | pluginId = tryItem.getPluginId() | ||||
| @@ -10,17 +10,21 @@ from qt_compat import qt_config | |||||
| if qt_config == 5: | if qt_config == 5: | ||||
| from PyQt5.QtCore import pyqtSignal, pyqtSlot, QT_VERSION, Qt, QPointF, QRectF, QSize, QTimer | from PyQt5.QtCore import pyqtSignal, pyqtSlot, QT_VERSION, Qt, QPointF, QRectF, QSize, QTimer | ||||
| from PyQt5.QtGui import QColor, QPainter, QPen | from PyQt5.QtGui import QColor, QPainter, QPen | ||||
| from PyQt5.QtWidgets import QGraphicsScene, QGraphicsSceneMouseEvent, QMainWindow | |||||
| from PyQt5.QtWidgets import QGraphicsScene, QGraphicsSceneMouseEvent, QMainWindow, QApplication | |||||
| elif qt_config == 6: | elif qt_config == 6: | ||||
| from PyQt6.QtCore import pyqtSignal, pyqtSlot, QT_VERSION, Qt, QPointF, QRectF, QSize, QTimer | from PyQt6.QtCore import pyqtSignal, pyqtSlot, QT_VERSION, Qt, QPointF, QRectF, QSize, QTimer | ||||
| from PyQt6.QtGui import QColor, QPainter, QPen | from PyQt6.QtGui import QColor, QPainter, QPen | ||||
| from PyQt6.QtWidgets import QGraphicsScene, QGraphicsSceneMouseEvent, QMainWindow | |||||
| from PyQt6.QtWidgets import QGraphicsScene, QGraphicsSceneMouseEvent, QMainWindow, QApplication | |||||
| import ctypes | |||||
| from time import sleep | |||||
| # ----------------------------------------------------------------------- | # ----------------------------------------------------------------------- | ||||
| # Imports (Custom) | # Imports (Custom) | ||||
| from carla_shared import * | from carla_shared import * | ||||
| from carla_utils import * | from carla_utils import * | ||||
| from widgets.scalabledial import ScalableDial | |||||
| import ui_xycontroller | import ui_xycontroller | ||||
| @@ -32,15 +36,21 @@ from externalui import ExternalUI | |||||
| from widgets.paramspinbox import ParamSpinBox | from widgets.paramspinbox import ParamSpinBox | ||||
| # ------------------------------------------------------------------------------------------------------------ | # ------------------------------------------------------------------------------------------------------------ | ||||
| XYCONTROLLER_PARAMETER_X = 0 | |||||
| XYCONTROLLER_PARAMETER_Y = 1 | |||||
| XYCONTROLLER_PARAMETER_SMOOTH = 0 | |||||
| XYCONTROLLER_PARAMETER_LINEAR = 1 | |||||
| XYCONTROLLER_PARAMETER_SPEED = 2 | |||||
| XYCONTROLLER_PARAMETER_REVERSEY = 3 | |||||
| XYCONTROLLER_PARAMETER_X = 4 | |||||
| XYCONTROLLER_PARAMETER_Y = 5 | |||||
| XYCONTROLLER_PARAMETER_OUT_X = 6 | |||||
| XYCONTROLLER_PARAMETER_OUT_Y = 7 | |||||
| # ------------------------------------------------------------------------------------------------------------ | # ------------------------------------------------------------------------------------------------------------ | ||||
| class XYGraphicsScene(QGraphicsScene): | class XYGraphicsScene(QGraphicsScene): | ||||
| # signals | # signals | ||||
| cursorMoved = pyqtSignal(float,float) | cursorMoved = pyqtSignal(float,float) | ||||
| knobsUpdate = pyqtSignal(float,float) | |||||
| def __init__(self, parent): | def __init__(self, parent): | ||||
| QGraphicsScene.__init__(self, parent) | QGraphicsScene.__init__(self, parent) | ||||
| @@ -52,9 +62,20 @@ class XYGraphicsScene(QGraphicsScene): | |||||
| self.m_channels = [] | self.m_channels = [] | ||||
| self.m_mouseLock = False | self.m_mouseLock = False | ||||
| self.m_smooth = False | self.m_smooth = False | ||||
| self.m_linear = False | |||||
| self.m_speed = 8.0 # 1.0 to 100.0 | |||||
| self.m_rSmooth = False # Using right button | |||||
| self.m_smooth_x = 0.0 | self.m_smooth_x = 0.0 | ||||
| self.m_smooth_y = 0.0 | self.m_smooth_y = 0.0 | ||||
| self.reverseY = 1.0 # -1.0 when reversed | |||||
| self.xpPrev = 0.0 | |||||
| self.ypPrev = 0.0 | |||||
| self.time: ctypes.c_uint64 = 0 # I sure just int is quite enough here, but... | |||||
| self.prevTime: ctypes.c_uint64 = 0 | |||||
| self.setBackgroundBrush(Qt.black) | self.setBackgroundBrush(Qt.black) | ||||
| cursorPen = QPen(QColor(255, 255, 255), 2) | cursorPen = QPen(QColor(255, 255, 255), 2) | ||||
| @@ -87,31 +108,31 @@ class XYGraphicsScene(QGraphicsScene): | |||||
| self.m_lineV.setX(posX) | self.m_lineV.setX(posX) | ||||
| if forward: | if forward: | ||||
| value = posX / (self.p_size.x() + self.p_size.width()); | |||||
| value = posX / (self.p_size.x() + self.p_size.width()) | |||||
| self.sendMIDI(value, None) | self.sendMIDI(value, None) | ||||
| else: | else: | ||||
| self.m_smooth_x = posX; | |||||
| self.m_smooth_x = posX | |||||
| def setPosY(self, y: float, forward: bool = True): | def setPosY(self, y: float, forward: bool = True): | ||||
| if self.m_mouseLock: | if self.m_mouseLock: | ||||
| return; | |||||
| return | |||||
| posY = y * (self.p_size.y() + self.p_size.height()) | |||||
| posY = y * (self.p_size.y() + self.p_size.height()) * self.reverseY | |||||
| self.m_cursor.setPos(self.m_cursor.x(), posY) | self.m_cursor.setPos(self.m_cursor.x(), posY) | ||||
| self.m_lineH.setY(posY) | self.m_lineH.setY(posY) | ||||
| if forward: | if forward: | ||||
| value = posY / (self.p_size.y() + self.p_size.height()) | |||||
| value = posY / (self.p_size.y() + self.p_size.height()) * self.reverseY | |||||
| self.sendMIDI(None, value) | self.sendMIDI(None, value) | ||||
| else: | else: | ||||
| self.m_smooth_y = posY | self.m_smooth_y = posY | ||||
| def setSmooth(self, smooth: bool): | |||||
| self.m_smooth = smooth | |||||
| def setSmoothValues(self, x: float, y: float): | def setSmoothValues(self, x: float, y: float): | ||||
| self.m_smooth_x = x * (self.p_size.x() + self.p_size.width()); | |||||
| self.m_smooth_y = y * (self.p_size.y() + self.p_size.height()); | |||||
| self.m_smooth_x = x * (self.p_size.x() + self.p_size.width()) | |||||
| self.m_smooth_y = y * (self.p_size.y() + self.p_size.height()) * self.reverseY | |||||
| def setReverseY(self, rev: bool): | |||||
| self.reverseY = 1 - (int(rev) * 2) # 1.0 or -1.0 | |||||
| # ------------------------------------------------------------------- | # ------------------------------------------------------------------- | ||||
| @@ -119,82 +140,132 @@ class XYGraphicsScene(QGraphicsScene): | |||||
| self.p_size.setRect(-(float(size.width())/2), | self.p_size.setRect(-(float(size.width())/2), | ||||
| -(float(size.height())/2), | -(float(size.height())/2), | ||||
| size.width(), | size.width(), | ||||
| size.height()); | |||||
| size.height()) | |||||
| def updatePos(self, pos: QPointF, filterSame: bool = False, knobsOnly: bool = False): | |||||
| xp = pos.x() / (self.p_size.x() + self.p_size.width()) | |||||
| yp = pos.y() / (self.p_size.y() + self.p_size.height()) * self.reverseY | |||||
| if knobsOnly: | |||||
| self.knobsUpdate.emit(xp * 100, yp * 100) | |||||
| return | |||||
| self.m_cursor.setPos(pos) | |||||
| self.m_lineH.setY(pos.y()) | |||||
| self.m_lineV.setX(pos.x()) | |||||
| # Set 0.05% precision, yet exact final value settling, esp. zero | |||||
| xp = round(xp * 1000) / 1000 | |||||
| yp = round(yp * 1000) / 1000 | |||||
| self.sendMIDI(xp, yp, filterSame) | |||||
| self.cursorMoved.emit(xp, yp) | |||||
| def updateSmooth(self): | |||||
| if not self.m_smooth: | |||||
| def updateSmooth(self, time): | |||||
| if not (self.m_smooth or self.m_rSmooth): | |||||
| return | return | ||||
| if self.m_cursor.x() == self.m_smooth_x and self.m_cursor.y() == self.m_smooth_y: | |||||
| dx = self.m_smooth_x - self.m_cursor.x() | |||||
| dy = self.m_smooth_y - self.m_cursor.y() | |||||
| if dx == dy == 0: | |||||
| return | return | ||||
| same = 0 | same = 0 | ||||
| if abs(self.m_cursor.x() - self.m_smooth_x) <= 0.0005: | |||||
| if abs(dx) <= 0.0005: | |||||
| self.m_smooth_x = self.m_cursor.x() | self.m_smooth_x = self.m_cursor.x() | ||||
| same += 1 | same += 1 | ||||
| if abs(self.m_cursor.y() - self.m_smooth_y) <= 0.0005: | |||||
| if abs(dy) <= 0.0005: | |||||
| self.m_smooth_y = self.m_cursor.y() | self.m_smooth_y = self.m_cursor.y() | ||||
| same += 1 | same += 1 | ||||
| if same == 2: | if same == 2: | ||||
| return | return | ||||
| newX = float(self.m_smooth_x + self.m_cursor.x()*7) / 8 | |||||
| newY = float(self.m_smooth_y + self.m_cursor.y()*7) / 8 | |||||
| pos = QPointF(newX, newY) | |||||
| speed = self.m_speed | |||||
| self.m_cursor.setPos(pos) | |||||
| self.m_lineH.setY(pos.y()) | |||||
| self.m_lineV.setX(pos.x()) | |||||
| mod = QApplication.keyboardModifiers() | |||||
| if (mod & Qt.ControlModifier): | |||||
| speed /= 2 | |||||
| elif (mod & Qt.ShiftModifier): | |||||
| speed *= 2 | |||||
| xp = pos.x() / (self.p_size.x() + self.p_size.width()) | |||||
| yp = pos.y() / (self.p_size.y() + self.p_size.height()) | |||||
| if self.m_linear: | |||||
| newX = self.m_cursor.x() + max(min(dx / speed, 1), -1) * speed | |||||
| newY = self.m_cursor.y() + max(min(dy / speed, 1), -1) * speed | |||||
| else: | |||||
| precision = 64 / speed | |||||
| newX = float(self.m_smooth_x + self.m_cursor.x()*(precision-1)) / precision | |||||
| newY = float(self.m_smooth_y + self.m_cursor.y()*(precision-1)) / precision | |||||
| self.sendMIDI(xp, yp) | |||||
| self.cursorMoved.emit(xp, yp) | |||||
| pos = QPointF(newX, newY) | |||||
| self.updatePos(pos, ((time - self.prevTime) == 1)) # Continuous calls or Not | |||||
| self.prevTime = time | |||||
| # ------------------------------------------------------------------- | # ------------------------------------------------------------------- | ||||
| def handleMousePos(self, pos: QPointF): | |||||
| def handleMousePos(self, event): | |||||
| if (event.buttons() & Qt.MiddleButton): | |||||
| pos = QPointF(0, 0) | |||||
| else: | |||||
| pos = QPointF(event.scenePos()) | |||||
| if not self.p_size.contains(pos): | if not self.p_size.contains(pos): | ||||
| if pos.x() < self.p_size.x(): | if pos.x() < self.p_size.x(): | ||||
| pos.setX(self.p_size.x()) | pos.setX(self.p_size.x()) | ||||
| elif pos.x() > (self.p_size.x() + self.p_size.width()): | elif pos.x() > (self.p_size.x() + self.p_size.width()): | ||||
| pos.setX(self.p_size.x() + self.p_size.width()); | |||||
| pos.setX(self.p_size.x() + self.p_size.width()) | |||||
| if pos.y() < self.p_size.y(): | if pos.y() < self.p_size.y(): | ||||
| pos.setY(self.p_size.y()) | pos.setY(self.p_size.y()) | ||||
| elif pos.y() > (self.p_size.y() + self.p_size.height()): | elif pos.y() > (self.p_size.y() + self.p_size.height()): | ||||
| pos.setY(self.p_size.y() + self.p_size.height()) | pos.setY(self.p_size.y() + self.p_size.height()) | ||||
| self.updatePos(pos, knobsOnly=True) | |||||
| self.m_smooth_x = pos.x() | self.m_smooth_x = pos.x() | ||||
| self.m_smooth_y = pos.y() | self.m_smooth_y = pos.y() | ||||
| if not self.m_smooth: | |||||
| self.m_cursor.setPos(pos) | |||||
| self.m_lineH.setY(pos.y()) | |||||
| self.m_lineV.setX(pos.x()) | |||||
| self.m_rSmooth = event.buttons() & Qt.RightButton | |||||
| xp = pos.x() / (self.p_size.x() + self.p_size.width()); | |||||
| yp = pos.y() / (self.p_size.y() + self.p_size.height()); | |||||
| # When not smooth, update each time; | |||||
| # (commented-out part) When smooth, update (re-send same) if click position is same (when smoothing not sends anything). (To match non-smoothed behaviour) | |||||
| if (not (self.m_smooth or self.m_rSmooth)): # or (abs(self.m_cursor.x() - self.m_smooth_x) <= 0.0005 and abs(self.m_cursor.y() - self.m_smooth_y) <= 0.0005): | |||||
| self.updatePos(pos) | |||||
| self.sendMIDI(xp, yp) | |||||
| self.cursorMoved.emit(xp, yp) | |||||
| def sendMIDI(self, xp, yp): | |||||
| def sendMIDI(self, xp, yp, filterSame = False): | |||||
| rate = float(0xff) / 4 | rate = float(0xff) / 4 | ||||
| msgd = ["cc2" if xp is not None and yp is not None else "cc"] | |||||
| prefix = [] | |||||
| msgd = [] | |||||
| if xp is not None: | if xp is not None: | ||||
| msgd.append(self.cc_x) | |||||
| msgd.append(int(xp * rate + rate)) | |||||
| value = int(xp * rate + rate) | |||||
| if not (filterSame and (value == self.xpPrev)): | |||||
| prefix = ["cc"] | |||||
| msgd.append(self.cc_x) | |||||
| msgd.append(value) | |||||
| self.xpPrev = value | |||||
| if yp is not None: | if yp is not None: | ||||
| msgd.append(self.cc_y) | |||||
| msgd.append(int(yp * rate + rate)) | |||||
| value = int(yp * rate + rate) | |||||
| if not (filterSame and (value == self.ypPrev)): | |||||
| if prefix == []: | |||||
| prefix = ["cc"] | |||||
| else: | |||||
| prefix = ["cc2"] | |||||
| msgd.append(self.cc_y) | |||||
| msgd.append(value) | |||||
| self.rparent.send(msgd) | |||||
| self.ypPrev = value | |||||
| if not (prefix == []): | |||||
| self.rparent.send(prefix + msgd) | |||||
| # ------------------------------------------------------------------- | # ------------------------------------------------------------------- | ||||
| @@ -206,13 +277,13 @@ class XYGraphicsScene(QGraphicsScene): | |||||
| def mousePressEvent(self, event: QGraphicsSceneMouseEvent): | def mousePressEvent(self, event: QGraphicsSceneMouseEvent): | ||||
| self.m_mouseLock = True | self.m_mouseLock = True | ||||
| self.handleMousePos(event.scenePos()) | |||||
| self.handleMousePos(event) | |||||
| self.rparent.setCursor(Qt.CrossCursor) | self.rparent.setCursor(Qt.CrossCursor) | ||||
| QGraphicsScene.mousePressEvent(self, event); | |||||
| QGraphicsScene.mousePressEvent(self, event) | |||||
| def mouseMoveEvent(self, event: QGraphicsSceneMouseEvent): | def mouseMoveEvent(self, event: QGraphicsSceneMouseEvent): | ||||
| self.handleMousePos(event.scenePos()) | |||||
| QGraphicsScene.mouseMoveEvent(self, event); | |||||
| self.handleMousePos(event) | |||||
| QGraphicsScene.mouseMoveEvent(self, event) | |||||
| def mouseReleaseEvent(self, event: QGraphicsSceneMouseEvent): | def mouseReleaseEvent(self, event: QGraphicsSceneMouseEvent): | ||||
| self.m_mouseLock = False | self.m_mouseLock = False | ||||
| @@ -231,15 +302,22 @@ class XYControllerUI(ExternalUI, QMainWindow): | |||||
| self.fSaveSizeNowChecker = -1 | self.fSaveSizeNowChecker = -1 | ||||
| self.isXActual = False | |||||
| self.isYActual = False | |||||
| # --------------------------------------------------------------- | # --------------------------------------------------------------- | ||||
| # Set-up GUI stuff | # Set-up GUI stuff | ||||
| self.scene = XYGraphicsScene(self) | self.scene = XYGraphicsScene(self) | ||||
| self.ui.dial_x.setImage(2) | |||||
| self.ui.dial_y.setImage(2) | |||||
| self.ui.dial_x.setLabel("X") | |||||
| self.ui.dial_y.setLabel("Y") | |||||
| # Now knobs are QWidgets, not QDials. | |||||
| self.ui.dial_x = ScalableDial(self.ui.dial_x, 0, 400, 0, -100, 100, "X", 64, -1, "%", "tube", 1, {}) | |||||
| self.ui.dial_y = ScalableDial(self.ui.dial_y, 1, 400, 0, -100, 100, "Y", 64, -1, "%", "tube", 1, {}) | |||||
| # These are outputs (7-seg displays). | |||||
| self.ui.dial_out_x = ScalableDial(self.ui.dial_out_x, 2, 400, 0, -100, 100, "Out X", 64, -1, "%", "tube", 1, {'Auto7segWidth':1, }, isOutput=True) | |||||
| self.ui.dial_out_y = ScalableDial(self.ui.dial_out_y, 3, 400, 0, -100, 100, "Out Y", 64, -1, "%", "tube", 1, {'Auto7segWidth':1, }, isOutput=True) | |||||
| self.ui.keyboard.setOctaves(10) | self.ui.keyboard.setOctaves(10) | ||||
| self.ui.graphicsView.setScene(self.scene) | self.ui.graphicsView.setScene(self.scene) | ||||
| @@ -262,6 +340,7 @@ class XYControllerUI(ExternalUI, QMainWindow): | |||||
| # Connect actions to functions | # Connect actions to functions | ||||
| self.scene.cursorMoved.connect(self.slot_sceneCursorMoved) | self.scene.cursorMoved.connect(self.slot_sceneCursorMoved) | ||||
| self.scene.knobsUpdate.connect(self.slot_setKnobs) | |||||
| self.ui.keyboard.noteOn.connect(self.slot_noteOn) | self.ui.keyboard.noteOn.connect(self.slot_noteOn) | ||||
| self.ui.keyboard.noteOff.connect(self.slot_noteOff) | self.ui.keyboard.noteOff.connect(self.slot_noteOff) | ||||
| @@ -271,6 +350,7 @@ class XYControllerUI(ExternalUI, QMainWindow): | |||||
| self.ui.dial_x.realValueChanged.connect(self.slot_knobValueChangedX) | self.ui.dial_x.realValueChanged.connect(self.slot_knobValueChangedX) | ||||
| self.ui.dial_y.realValueChanged.connect(self.slot_knobValueChangedY) | self.ui.dial_y.realValueChanged.connect(self.slot_knobValueChangedY) | ||||
| if QT_VERSION >= 0x60000: | if QT_VERSION >= 0x60000: | ||||
| self.ui.cb_control_x.currentTextChanged.connect(self.slot_checkCC_X) | self.ui.cb_control_x.currentTextChanged.connect(self.slot_checkCC_X) | ||||
| self.ui.cb_control_y.currentTextChanged.connect(self.slot_checkCC_Y) | self.ui.cb_control_y.currentTextChanged.connect(self.slot_checkCC_Y) | ||||
| @@ -308,17 +388,23 @@ class XYControllerUI(ExternalUI, QMainWindow): | |||||
| # ------------------------------------------------------------------- | # ------------------------------------------------------------------- | ||||
| def setSmooth(self, smooth: bool): | |||||
| self.scene.m_smooth = smooth | |||||
| x = self.ui.dial_x.rvalue() / 100 | |||||
| y = self.ui.dial_y.rvalue() / 100 | |||||
| if smooth: | |||||
| self.scene.setSmoothValues(x, y) | |||||
| else: | |||||
| self.scene.setPosX(x, True) | |||||
| self.scene.setPosY(y, True) | |||||
| self.slot_sceneCursorMoved(x, y) | |||||
| @pyqtSlot() | @pyqtSlot() | ||||
| def slot_updateScreen(self): | def slot_updateScreen(self): | ||||
| self.ui.graphicsView.centerOn(0, 0) | self.ui.graphicsView.centerOn(0, 0) | ||||
| self.scene.updateSize(self.ui.graphicsView.size()) | self.scene.updateSize(self.ui.graphicsView.size()) | ||||
| dial_x = self.ui.dial_x.rvalue() | |||||
| dial_y = self.ui.dial_y.rvalue() | |||||
| self.scene.setPosX(dial_x / 100, False) | |||||
| self.scene.setPosY(dial_y / 100, False) | |||||
| self.scene.setSmoothValues(dial_x / 100, dial_y / 100) | |||||
| @pyqtSlot(int) | @pyqtSlot(int) | ||||
| def slot_noteOn(self, note): | def slot_noteOn(self, note): | ||||
| self.send(["note", True, note]) | self.send(["note", True, note]) | ||||
| @@ -328,16 +414,34 @@ class XYControllerUI(ExternalUI, QMainWindow): | |||||
| self.send(["note", False, note]) | self.send(["note", False, note]) | ||||
| @pyqtSlot(float) | @pyqtSlot(float) | ||||
| def slot_knobValueChangedX(self, x: float): | |||||
| self.sendControl(XYCONTROLLER_PARAMETER_X, x) | |||||
| self.scene.setPosX(x / 100, True) | |||||
| self.scene.setSmoothValues(x / 100, self.ui.dial_y.rvalue() / 100) | |||||
| def slot_knobValueChangedX(self, x:float, external:bool=False, firstRun:bool=False): | |||||
| if not external: | |||||
| self.sendControl(XYCONTROLLER_PARAMETER_X, x) | |||||
| else: | |||||
| self.ui.dial_x.setValue(x, False) | |||||
| if (not self.scene.m_smooth) or firstRun: | |||||
| self.sendControl(XYCONTROLLER_PARAMETER_OUT_X, x) | |||||
| self.ui.dial_out_x.setValue(x, False) | |||||
| self.scene.setPosX(x / 100, True) | |||||
| else: | |||||
| self.scene.setSmoothValues(x / 100, self.ui.dial_y.rvalue() / 100) | |||||
| @pyqtSlot(float) | @pyqtSlot(float) | ||||
| def slot_knobValueChangedY(self, y: float): | |||||
| self.sendControl(XYCONTROLLER_PARAMETER_Y, y) | |||||
| self.scene.setPosY(y / 100, True) | |||||
| self.scene.setSmoothValues(self.ui.dial_x.rvalue() / 100, y / 100) | |||||
| def slot_knobValueChangedY(self, y:float, external:bool=False, firstRun:bool=False): | |||||
| if not external: | |||||
| self.sendControl(XYCONTROLLER_PARAMETER_Y, y) | |||||
| else: | |||||
| self.ui.dial_y.setValue(y, False) | |||||
| if (not self.scene.m_smooth) or firstRun: | |||||
| self.sendControl(XYCONTROLLER_PARAMETER_OUT_Y, y) | |||||
| self.ui.dial_out_y.setValue(y, False) | |||||
| self.scene.setPosY(y / 100, True) | |||||
| else: | |||||
| self.scene.setSmoothValues(self.ui.dial_x.rvalue() / 100, y / 100) | |||||
| @pyqtSlot(str) | @pyqtSlot(str) | ||||
| def slot_checkCC_X(self, text: str): | def slot_checkCC_X(self, text: str): | ||||
| @@ -422,20 +526,16 @@ class XYControllerUI(ExternalUI, QMainWindow): | |||||
| @pyqtSlot(bool) | @pyqtSlot(bool) | ||||
| def slot_setSmooth(self, smooth): | def slot_setSmooth(self, smooth): | ||||
| self.scene.setSmooth(smooth) | |||||
| self.setSmooth(smooth) | |||||
| self.sendConfigure("smooth", "yes" if smooth else "no") | self.sendConfigure("smooth", "yes" if smooth else "no") | ||||
| if smooth: | |||||
| dial_x = self.ui.dial_x.rvalue() | |||||
| dial_y = self.ui.dial_y.rvalue() | |||||
| self.scene.setSmoothValues(dial_x / 100, dial_y / 100) | |||||
| self.sendControl(XYCONTROLLER_PARAMETER_SMOOTH, int(smooth)) | |||||
| @pyqtSlot(float, float) | @pyqtSlot(float, float) | ||||
| def slot_sceneCursorMoved(self, xp: float, yp: float): | def slot_sceneCursorMoved(self, xp: float, yp: float): | ||||
| self.ui.dial_x.setValue(xp * 100, False) | |||||
| self.ui.dial_y.setValue(yp * 100, False) | |||||
| self.sendControl(XYCONTROLLER_PARAMETER_X, xp * 100) | |||||
| self.sendControl(XYCONTROLLER_PARAMETER_Y, yp * 100) | |||||
| self.ui.dial_out_x.setValue(xp * 100, False) | |||||
| self.ui.dial_out_y.setValue(yp * 100, False) | |||||
| self.sendControl(XYCONTROLLER_PARAMETER_OUT_X, xp * 100) | |||||
| self.sendControl(XYCONTROLLER_PARAMETER_OUT_Y, yp * 100) | |||||
| @pyqtSlot(bool) | @pyqtSlot(bool) | ||||
| def slot_showKeyboard(self, yesno): | def slot_showKeyboard(self, yesno): | ||||
| @@ -443,21 +543,47 @@ class XYControllerUI(ExternalUI, QMainWindow): | |||||
| self.sendConfigure("show-midi-keyboard", "yes" if yesno else "no") | self.sendConfigure("show-midi-keyboard", "yes" if yesno else "no") | ||||
| QTimer.singleShot(0, self.slot_updateScreen) | QTimer.singleShot(0, self.slot_updateScreen) | ||||
| @pyqtSlot(float, float) | |||||
| def slot_setKnobs(self, x, y): | |||||
| self.ui.dial_x.setValue(x, False) | |||||
| self.ui.dial_y.setValue(y, False) | |||||
| self.sendControl(XYCONTROLLER_PARAMETER_X, x) | |||||
| self.sendControl(XYCONTROLLER_PARAMETER_Y, y) | |||||
| # ------------------------------------------------------------------- | # ------------------------------------------------------------------- | ||||
| # DSP Callbacks | # DSP Callbacks | ||||
| # NOTE It called continuously with params 6, 7 (outs, not used here), is this good? | |||||
| def dspParameterChanged(self, index: int, value: float): | def dspParameterChanged(self, index: int, value: float): | ||||
| if index == XYCONTROLLER_PARAMETER_X: | |||||
| self.ui.dial_x.setValue(value, False) | |||||
| self.scene.setPosX(value / 100, False) | |||||
| if index == XYCONTROLLER_PARAMETER_SMOOTH: | |||||
| self.ui.cb_smooth.blockSignals(True) | |||||
| self.ui.cb_smooth.setChecked(bool(value)) | |||||
| self.ui.cb_smooth.blockSignals(False) | |||||
| self.setSmooth(bool(value)) | |||||
| elif index == XYCONTROLLER_PARAMETER_LINEAR: | |||||
| self.scene.m_linear = bool(value) | |||||
| elif index == XYCONTROLLER_PARAMETER_SPEED: | |||||
| self.scene.m_speed = value | |||||
| elif index == XYCONTROLLER_PARAMETER_REVERSEY: | |||||
| self.scene.setReverseY(bool(value)) | |||||
| elif index == XYCONTROLLER_PARAMETER_X: | |||||
| self.slot_knobValueChangedX(value, True, not self.isXActual) | |||||
| self.isXActual = True | |||||
| elif index == XYCONTROLLER_PARAMETER_Y: | elif index == XYCONTROLLER_PARAMETER_Y: | ||||
| self.ui.dial_y.setValue(value, False) | |||||
| self.scene.setPosY(value / 100, False) | |||||
| self.slot_knobValueChangedY(value, True, not self.isYActual) | |||||
| if self.isXActual and not self.isYActual: | |||||
| # Run it once, BUT when both X & Y are received (they can be shuffled). | |||||
| self.scene.setSmoothValues(self.ui.dial_x.rvalue() / 100, self.ui.dial_y.rvalue() / 100) | |||||
| self.isYActual = True | |||||
| else: | else: | ||||
| return | return | ||||
| self.scene.setSmoothValues(self.ui.dial_x.rvalue() / 100, | |||||
| self.ui.dial_y.rvalue() / 100) | |||||
| def dspStateChanged(self, key: str, value: str): | def dspStateChanged(self, key: str, value: str): | ||||
| if key == "guiWidth": | if key == "guiWidth": | ||||
| @@ -480,15 +606,7 @@ class XYControllerUI(ExternalUI, QMainWindow): | |||||
| elif key == "smooth": | elif key == "smooth": | ||||
| smooth = (value == "yes") | smooth = (value == "yes") | ||||
| self.ui.cb_smooth.blockSignals(True) | |||||
| self.ui.cb_smooth.setChecked(smooth) | |||||
| self.ui.cb_smooth.blockSignals(False) | |||||
| self.scene.setSmooth(smooth) | |||||
| if smooth: | |||||
| dial_x = self.ui.dial_x.rvalue() | |||||
| dial_y = self.ui.dial_y.rvalue() | |||||
| self.scene.setSmoothValues(dial_x / 100, dial_y / 100) | |||||
| self.setSmooth(bool(smooth)) | |||||
| elif key == "show-midi-keyboard": | elif key == "show-midi-keyboard": | ||||
| show = (value == "yes") | show = (value == "yes") | ||||
| @@ -587,9 +705,10 @@ class XYControllerUI(ExternalUI, QMainWindow): | |||||
| QMainWindow.resizeEvent(self, event) | QMainWindow.resizeEvent(self, event) | ||||
| def timerEvent(self, event): | def timerEvent(self, event): | ||||
| self.scene.time += 1 | |||||
| if event.timerId() == self.fIdleTimer: | if event.timerId() == self.fIdleTimer: | ||||
| self.idleExternalUI() | self.idleExternalUI() | ||||
| self.scene.updateSmooth() | |||||
| self.scene.updateSmooth(self.scene.time) | |||||
| if self.fSaveSizeNowChecker == 11: | if self.fSaveSizeNowChecker == 11: | ||||
| self.sendConfigure("guiWidth", str(self.width())) | self.sendConfigure("guiWidth", str(self.width())) | ||||
| @@ -29,6 +29,10 @@ class XYControllerPlugin : public NativePluginAndUiClass | |||||
| { | { | ||||
| public: | public: | ||||
| enum Parameters { | enum Parameters { | ||||
| kParamSmooth, | |||||
| kParamLinear, | |||||
| kParamSpeed, | |||||
| kParamReverseY, | |||||
| kParamInX, | kParamInX, | ||||
| kParamInY, | kParamInY, | ||||
| kParamOutX, | kParamOutX, | ||||
| @@ -44,6 +48,7 @@ public: | |||||
| mqueueRT() | mqueueRT() | ||||
| { | { | ||||
| carla_zeroStruct(params); | carla_zeroStruct(params); | ||||
| params[kParamSpeed] = 8.0f; | |||||
| carla_zeroStruct(channels); | carla_zeroStruct(channels); | ||||
| channels[0] = true; | channels[0] = true; | ||||
| } | } | ||||
| @@ -92,6 +97,56 @@ protected: | |||||
| hints |= NATIVE_PARAMETER_IS_OUTPUT; | hints |= NATIVE_PARAMETER_IS_OUTPUT; | ||||
| param.name = "Out Y"; | param.name = "Out Y"; | ||||
| break; | break; | ||||
| case kParamSpeed: | |||||
| param.name = "Speed"; | |||||
| param.unit = "px"; | |||||
| param.ranges.def = 8.0f; | |||||
| param.ranges.min = 1.0f; | |||||
| break; | |||||
| } | |||||
| if (param.name == nullptr) | |||||
| { | |||||
| hints |= NATIVE_PARAMETER_IS_INTEGER | NATIVE_PARAMETER_USES_SCALEPOINTS; | |||||
| param.unit = nullptr; | |||||
| param.ranges.min = 0; | |||||
| param.ranges.max = 1; | |||||
| param.scalePointCount = 2; | |||||
| switch (index) | |||||
| { | |||||
| case kParamSmooth: | |||||
| param.name = "Smooth"; | |||||
| { | |||||
| static const NativeParameterScalePoint scalePoints[2] = { | |||||
| { "Thru", 0 }, | |||||
| { "Smooth", 1 } | |||||
| }; | |||||
| param.scalePoints = scalePoints; | |||||
| } | |||||
| break; | |||||
| case kParamLinear: | |||||
| param.name = "Linear"; | |||||
| { | |||||
| static const NativeParameterScalePoint scalePoints[2] = { | |||||
| { "Log", 0 }, | |||||
| { "Linear", 1 } | |||||
| }; | |||||
| param.scalePoints = scalePoints; | |||||
| } | |||||
| break; | |||||
| case kParamReverseY: | |||||
| param.name = "Rev Y"; | |||||
| { | |||||
| static const NativeParameterScalePoint scalePoints[2] = { | |||||
| { "Top to Bottom", 0 }, | |||||
| { "Bottom to Top", 1 } | |||||
| }; | |||||
| param.scalePoints = scalePoints; | |||||
| } | |||||
| break; | |||||
| } | |||||
| } | } | ||||
| param.hints = static_cast<NativeParameterHints>(hints); | param.hints = static_cast<NativeParameterHints>(hints); | ||||
| @@ -113,8 +168,14 @@ protected: | |||||
| { | { | ||||
| switch (index) | switch (index) | ||||
| { | { | ||||
| case kParamSmooth: | |||||
| case kParamLinear: | |||||
| case kParamSpeed: | |||||
| case kParamReverseY: | |||||
| case kParamInX: | case kParamInX: | ||||
| case kParamInY: | case kParamInY: | ||||
| case kParamOutX: | |||||
| case kParamOutY: | |||||
| params[index] = value; | params[index] = value; | ||||
| break; | break; | ||||
| } | } | ||||
| @@ -147,8 +208,8 @@ protected: | |||||
| void process(const float* const*, float**, const uint32_t, | void process(const float* const*, float**, const uint32_t, | ||||
| const NativeMidiEvent* const midiEvents, const uint32_t midiEventCount) override | const NativeMidiEvent* const midiEvents, const uint32_t midiEventCount) override | ||||
| { | { | ||||
| params[kParamOutX] = params[kParamInX]; | |||||
| params[kParamOutY] = params[kParamInY]; | |||||
| // params[kParamOutX] = params[kParamInX]; | |||||
| // params[kParamOutY] = params[kParamInY]; | |||||
| if (mqueue.isNotEmpty() && mqueueRT.tryToCopyDataFrom(mqueue)) | if (mqueue.isNotEmpty() && mqueueRT.tryToCopyDataFrom(mqueue)) | ||||
| { | { | ||||
| @@ -266,7 +327,7 @@ static const NativePluginDescriptor notesDesc = { | |||||
| /* audioOuts */ 0, | /* audioOuts */ 0, | ||||
| /* midiIns */ 1, | /* midiIns */ 1, | ||||
| /* midiOuts */ 1, | /* midiOuts */ 1, | ||||
| /* paramIns */ 2, | |||||
| /* paramIns */ 6, | |||||
| /* paramOuts */ 2, | /* paramOuts */ 2, | ||||
| /* name */ "XY Controller", | /* name */ "XY Controller", | ||||
| /* label */ "xycontroller", | /* label */ "xycontroller", | ||||