| @@ -3,9 +3,15 @@ | |||
| <file>16x16/carla.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/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/compact.svgz</file> | |||
| <file>16x16/configure.svgz</file> | |||
| <file>16x16/dialog-cancel.svgz</file> | |||
| <file>16x16/dialog-error.svgz</file> | |||
| @@ -16,9 +22,11 @@ | |||
| <file>16x16/document-open.svgz</file> | |||
| <file>16x16/document-save.svgz</file> | |||
| <file>16x16/document-save-as.svgz</file> | |||
| <file>16x16/dry.svgz</file> | |||
| <file>16x16/edit-clear.svgz</file> | |||
| <file>16x16/edit-delete.svgz</file> | |||
| <file>16x16/edit-rename.svgz</file> | |||
| <file>16x16/emblem-favorite.svgz</file> | |||
| <file>16x16/list-add.svgz</file> | |||
| <file>16x16/list-remove.svgz</file> | |||
| <file>16x16/media-playback-pause.svgz</file> | |||
| @@ -27,8 +35,14 @@ | |||
| <file>16x16/media-seek-backward.svgz</file> | |||
| <file>16x16/media-seek-forward.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-purple.svgz</file> | |||
| <file>16x16/view-sort-ascending.svgz</file> | |||
| <file>16x16/wet.svgz</file> | |||
| <file>16x16/window-close.svgz</file> | |||
| <file>16x16/zoom-fit-best.svgz</file> | |||
| <file>16x16/zoom-in.svgz</file> | |||
| @@ -108,17 +108,17 @@ | |||
| </spacer> | |||
| </item> | |||
| <item> | |||
| <widget class="ScalableDial" name="dial_drywet"> | |||
| <widget class="QWidget" name="dial_drywet"> | |||
| <property name="minimumSize"> | |||
| <size> | |||
| <width>34</width> | |||
| <height>34</height> | |||
| <width>32</width> | |||
| <height>48</height> | |||
| </size> | |||
| </property> | |||
| <property name="maximumSize"> | |||
| <size> | |||
| <width>34</width> | |||
| <height>34</height> | |||
| <width>32</width> | |||
| <height>48</height> | |||
| </size> | |||
| </property> | |||
| <property name="contextMenuPolicy"> | |||
| @@ -130,17 +130,17 @@ | |||
| </widget> | |||
| </item> | |||
| <item> | |||
| <widget class="ScalableDial" name="dial_vol"> | |||
| <widget class="QWidget" name="dial_vol"> | |||
| <property name="minimumSize"> | |||
| <size> | |||
| <width>34</width> | |||
| <height>34</height> | |||
| <width>32</width> | |||
| <height>48</height> | |||
| </size> | |||
| </property> | |||
| <property name="maximumSize"> | |||
| <size> | |||
| <width>34</width> | |||
| <height>34</height> | |||
| <width>32</width> | |||
| <height>48</height> | |||
| </size> | |||
| </property> | |||
| <property name="contextMenuPolicy"> | |||
| @@ -152,148 +152,92 @@ | |||
| </widget> | |||
| </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"> | |||
| <size> | |||
| <width>16777215</width> | |||
| <height>42</height> | |||
| <width>26</width> | |||
| <height>40</height> | |||
| </size> | |||
| </property> | |||
| <property name="lineWidth"> | |||
| <number>0</number> | |||
| <property name="contextMenuPolicy"> | |||
| <enum>Qt::CustomContextMenu</enum> | |||
| </property> | |||
| <property name="currentIndex"> | |||
| <number>0</number> | |||
| <property name="statusTip"> | |||
| <string>Balance Left (0%)</string> | |||
| </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> | |||
| </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> | |||
| <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> | |||
| <spacer name="horizontalSpacer_4"> | |||
| @@ -310,6 +254,21 @@ | |||
| </item> | |||
| </layout> | |||
| </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> | |||
| </widget> | |||
| </item> | |||
| @@ -876,13 +835,6 @@ Plugin Name | |||
| </item> | |||
| </layout> | |||
| </widget> | |||
| <customwidgets> | |||
| <customwidget> | |||
| <class>ScalableDial</class> | |||
| <extends>QDial</extends> | |||
| <header>widgets/scalabledial.h</header> | |||
| </customwidget> | |||
| </customwidgets> | |||
| <resources> | |||
| <include location="../resources.qrc"/> | |||
| </resources> | |||
| @@ -474,6 +474,7 @@ | |||
| <addaction name="act_file_refresh"/> | |||
| <addaction name="act_file_new"/> | |||
| <addaction name="act_file_open"/> | |||
| <addaction name="act_file_reload"/> | |||
| <addaction name="act_file_save"/> | |||
| <addaction name="act_file_save_as"/> | |||
| <addaction name="separator"/> | |||
| @@ -508,6 +509,7 @@ | |||
| <addaction name="separator"/> | |||
| <addaction name="act_plugins_center"/> | |||
| <addaction name="separator"/> | |||
| <addaction name="act_plugins_change_skin"/> | |||
| <addaction name="act_plugins_compact"/> | |||
| <addaction name="act_plugins_expand"/> | |||
| </widget> | |||
| @@ -548,6 +550,7 @@ | |||
| <string>&Settings</string> | |||
| </property> | |||
| <addaction name="act_settings_show_toolbar"/> | |||
| <addaction name="act_settings_show_toolbar_text"/> | |||
| <addaction name="act_settings_show_meters"/> | |||
| <addaction name="act_settings_show_keyboard"/> | |||
| <addaction name="act_settings_show_side_panel"/> | |||
| @@ -589,6 +592,7 @@ | |||
| </attribute> | |||
| <addaction name="act_file_new"/> | |||
| <addaction name="act_file_open"/> | |||
| <addaction name="act_file_reload"/> | |||
| <addaction name="act_file_save"/> | |||
| <addaction name="act_file_save_as"/> | |||
| <addaction name="act_file_connect"/> | |||
| @@ -600,6 +604,17 @@ | |||
| <addaction name="act_engine_panic"/> | |||
| <addaction name="separator"/> | |||
| <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 class="QDockWidget" name="dockWidget"> | |||
| <property name="sizePolicy"> | |||
| @@ -1111,6 +1126,30 @@ | |||
| <enum>QAction::NoRole</enum> | |||
| </property> | |||
| </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"> | |||
| <property name="icon"> | |||
| <iconset resource="../resources.qrc"> | |||
| @@ -1220,6 +1259,10 @@ | |||
| </property> | |||
| </action> | |||
| <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"> | |||
| <string>Enable</string> | |||
| </property> | |||
| @@ -1228,6 +1271,10 @@ | |||
| </property> | |||
| </action> | |||
| <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"> | |||
| <string>Disable</string> | |||
| </property> | |||
| @@ -1236,6 +1283,10 @@ | |||
| </property> | |||
| </action> | |||
| <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"> | |||
| <string>0% Wet (Bypass)</string> | |||
| </property> | |||
| @@ -1244,6 +1295,10 @@ | |||
| </property> | |||
| </action> | |||
| <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"> | |||
| <string>100% Wet</string> | |||
| </property> | |||
| @@ -1252,6 +1307,10 @@ | |||
| </property> | |||
| </action> | |||
| <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"> | |||
| <string>0% Volume (Mute)</string> | |||
| </property> | |||
| @@ -1260,6 +1319,10 @@ | |||
| </property> | |||
| </action> | |||
| <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"> | |||
| <string>100% Volume</string> | |||
| </property> | |||
| @@ -1268,6 +1331,10 @@ | |||
| </property> | |||
| </action> | |||
| <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"> | |||
| <string>Center Balance</string> | |||
| </property> | |||
| @@ -1435,6 +1502,17 @@ | |||
| <enum>QAction::NoRole</enum> | |||
| </property> | |||
| </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"> | |||
| <property name="icon"> | |||
| <iconset resource="../resources.qrc"> | |||
| @@ -1553,7 +1631,23 @@ | |||
| <enum>QAction::NoRole</enum> | |||
| </property> | |||
| </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"> | |||
| <property name="icon"> | |||
| <iconset resource="../resources.qrc"> | |||
| <normaloff>:/16x16/compact.svgz</normaloff>:/16x16/compact.svgz</iconset> | |||
| </property> | |||
| <property name="text"> | |||
| <string>Compact Slots</string> | |||
| </property> | |||
| @@ -1562,6 +1656,10 @@ | |||
| </property> | |||
| </action> | |||
| <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"> | |||
| <string>Expand Slots</string> | |||
| </property> | |||
| @@ -1597,7 +1695,7 @@ | |||
| <action name="act_plugin_add_jack"> | |||
| <property name="icon"> | |||
| <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 name="text"> | |||
| <string>Add &JACK Application...</string> | |||
| @@ -511,6 +511,27 @@ | |||
| </property> | |||
| </spacer> | |||
| </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> | |||
| </widget> | |||
| </item> | |||
| @@ -30,41 +30,117 @@ | |||
| <item> | |||
| <layout class="QVBoxLayout" name="verticalLayout"> | |||
| <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 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> | |||
| </widget> | |||
| </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"> | |||
| <enum>Qt::Vertical</enum> | |||
| </property> | |||
| <property name="sizeType"> | |||
| <enum>QSizePolicy::Fixed</enum> | |||
| </property> | |||
| <property name="sizeHint" stdset="0"> | |||
| <property name="minimumSize"> | |||
| <size> | |||
| <width>20</width> | |||
| <height>30</height> | |||
| <width>48</width> | |||
| <height>0</height> | |||
| </size> | |||
| </property> | |||
| </spacer> | |||
| </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 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> | |||
| </widget> | |||
| </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> | |||
| </item> | |||
| </layout> | |||
| @@ -947,7 +947,7 @@ public: | |||
| pData->hints |= PLUGIN_USES_MULTI_PROGS; | |||
| if (! kUse16Outs) | |||
| pData->hints |= PLUGIN_CAN_BALANCE; | |||
| pData->hints |= PLUGIN_CAN_BALANCE | PLUGIN_CAN_PANNING; | |||
| // extra plugin hints | |||
| pData->extraHints = 0x0; | |||
| @@ -1521,7 +1521,7 @@ public: | |||
| { | |||
| // 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)); | |||
| 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 | |||
| if (kUse16Outs) | |||
| { | |||
| 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) | |||
| { | |||
| 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) | |||
| pData->hints |= PLUGIN_CAN_BALANCE; | |||
| if (aOuts >= 2) | |||
| pData->hints |= PLUGIN_CAN_PANNING; | |||
| #endif | |||
| // extra plugin hints | |||
| @@ -2083,7 +2086,11 @@ public: | |||
| 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 | |||
| 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) | |||
| { | |||
| 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) | |||
| pData->hints |= PLUGIN_CAN_BALANCE; | |||
| if (aOuts >= 2) | |||
| pData->hints |= PLUGIN_CAN_PANNING; | |||
| // extra plugin hints | |||
| pData->extraHints = 0x0; | |||
| @@ -4684,7 +4687,11 @@ public: | |||
| 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 | |||
| 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) | |||
| { | |||
| 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 | |||
| @@ -1405,6 +1405,9 @@ public: | |||
| if (aOuts >= 2 && aOuts % 2 == 0) | |||
| pData->hints |= PLUGIN_CAN_BALANCE; | |||
| if (aOuts >= 2) | |||
| pData->hints |= PLUGIN_CAN_PANNING; | |||
| // native plugin hints | |||
| if (fDescriptor->hints & NATIVE_PLUGIN_IS_RTSAFE) | |||
| pData->hints |= PLUGIN_IS_RTSAFE; | |||
| @@ -2369,7 +2372,7 @@ public: | |||
| float bufValue; | |||
| float* const oldBufLeft = pData->postProc.extraBuffer; | |||
| for (; i < pData->audioOut.count; ++i) | |||
| for (uint32_t i=0; i < pData->audioOut.count; ++i) | |||
| { | |||
| // Dry/Wet | |||
| if (doDryWet) | |||
| @@ -2380,7 +2383,11 @@ public: | |||
| 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 | |||
| 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) | |||
| { | |||
| 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. | |||
| 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 | |||
| # Various flags for parameter mappings. | |||
| @@ -54,6 +54,7 @@ if qt_config == 5: | |||
| QListWidgetItem, | |||
| QGraphicsView, | |||
| QMainWindow, | |||
| QToolButton, | |||
| ) | |||
| elif qt_config == 6: | |||
| @@ -86,6 +87,7 @@ elif qt_config == 6: | |||
| QListWidgetItem, | |||
| QGraphicsView, | |||
| QMainWindow, | |||
| QToolButton, | |||
| ) | |||
| # ------------------------------------------------------------------------------------------------------------ | |||
| @@ -101,6 +103,7 @@ from carla_shared import * | |||
| from carla_settings import * | |||
| from carla_utils import * | |||
| from carla_widgets import * | |||
| from carla_skin import * | |||
| from patchcanvas import patchcanvas | |||
| from widgets.digitalpeakmeter import DigitalPeakMeter | |||
| @@ -221,6 +224,8 @@ class HostWindow(QMainWindow): | |||
| self.fOscAddressTCP = "" | |||
| self.fOscAddressUDP = "" | |||
| self.slowTimer = 0 | |||
| if CARLA_OS_MAC: | |||
| self.fMacClosingHelper = True | |||
| @@ -270,6 +275,8 @@ class HostWindow(QMainWindow): | |||
| self.fWithCanvas = withCanvas | |||
| self.fTweaks = {} | |||
| # ---------------------------------------------------------------------------------------------------- | |||
| # Internal stuff (logs) | |||
| @@ -293,6 +300,7 @@ class HostWindow(QMainWindow): | |||
| if self.host.isControl: | |||
| self.ui.act_file_new.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.tabUtils.removeTab(0) | |||
| else: | |||
| @@ -319,10 +327,12 @@ class HostWindow(QMainWindow): | |||
| self.ui.act_file_new.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_as.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.setVisible(False) | |||
| @@ -503,6 +513,7 @@ class HostWindow(QMainWindow): | |||
| 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_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_as.setIcon(getIcon('document-save-as', 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_config.setIcon(getIcon('configure', 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_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_refresh.setIcon(getIcon('view-refresh', 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_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_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_bypass.triggered.connect(self.slot_pluginsBypass) | |||
| 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_expand.triggered.connect(self.slot_pluginsExpand) | |||
| 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_keyboard.toggled.connect(self.slot_showCanvasKeyboard) | |||
| 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) | |||
| pitem.recreateWidget(newColor = color) | |||
| def changePluginSkin(self, pluginId, skin): | |||
| def changePluginSkin(self, pluginId, skin, color = None): | |||
| if pluginId > self.fPluginCount: | |||
| return | |||
| @@ -748,7 +772,9 @@ class HostWindow(QMainWindow): | |||
| return | |||
| 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)) | |||
| else: | |||
| pitem.recreateWidget(newSkin = skin) | |||
| @@ -935,6 +961,16 @@ class HostWindow(QMainWindow): | |||
| self.loadProjectNow() | |||
| 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() | |||
| def slot_fileSave(self, saveAs=False): | |||
| if self.fProjectFilename and not saveAs: | |||
| @@ -1177,6 +1213,7 @@ class HostWindow(QMainWindow): | |||
| if self.host.isPlugin or not self.fSessionManagerName: | |||
| self.ui.act_file_open.setEnabled(False) | |||
| self.ui.act_file_reload.setEnabled(False) | |||
| self.ui.act_file_save_as.setEnabled(False) | |||
| @pyqtSlot(int, str) | |||
| @@ -1226,7 +1263,8 @@ class HostWindow(QMainWindow): | |||
| # Plugins | |||
| def removeAllPlugins(self): | |||
| self.ui.act_plugin_remove_all.setEnabled(False) | |||
| # self.ui.act_plugin_remove_all.setEnabled(False) | |||
| self.setMenuMacrosEnabled(False) | |||
| patchcanvas.handleAllPluginsRemoved() | |||
| while self.ui.listWidget.takeItem(0): | |||
| @@ -1343,6 +1381,12 @@ class HostWindow(QMainWindow): | |||
| act = fmenu.addAction(p['name']) | |||
| act.setData(p) | |||
| 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.addAction(self.ui.act_plugin_remove_all) | |||
| @@ -1359,6 +1403,7 @@ class HostWindow(QMainWindow): | |||
| menu.addSeparator() | |||
| menu.addAction(self.ui.act_plugins_center) | |||
| menu.addSeparator() | |||
| menu.addAction(self.ui.act_plugins_change_skin) | |||
| menu.addAction(self.ui.act_plugins_compact) | |||
| menu.addAction(self.ui.act_plugins_expand) | |||
| @@ -1454,7 +1499,7 @@ class HostWindow(QMainWindow): | |||
| if pitem is None: | |||
| break | |||
| pitem.getWidget().setInternalParameter(PLUGIN_CAN_VOLUME, 1.0) | |||
| pitem.getWidget().setInternalParameter(PARAMETER_VOLUME, 1.0) | |||
| @pyqtSlot() | |||
| def slot_pluginsMute(self): | |||
| @@ -1465,7 +1510,7 @@ class HostWindow(QMainWindow): | |||
| if pitem is None: | |||
| break | |||
| pitem.getWidget().setInternalParameter(PLUGIN_CAN_VOLUME, 0.0) | |||
| pitem.getWidget().setInternalParameter(PARAMETER_VOLUME, 0.0) | |||
| @pyqtSlot() | |||
| def slot_pluginsWet100(self): | |||
| @@ -1476,7 +1521,7 @@ class HostWindow(QMainWindow): | |||
| if pitem is None: | |||
| break | |||
| pitem.getWidget().setInternalParameter(PLUGIN_CAN_DRYWET, 1.0) | |||
| pitem.getWidget().setInternalParameter(PARAMETER_DRYWET, 1.0) | |||
| @pyqtSlot() | |||
| def slot_pluginsBypass(self): | |||
| @@ -1487,7 +1532,7 @@ class HostWindow(QMainWindow): | |||
| if pitem is None: | |||
| break | |||
| pitem.getWidget().setInternalParameter(PLUGIN_CAN_DRYWET, 0.0) | |||
| pitem.getWidget().setInternalParameter(PARAMETER_DRYWET, 0.0) | |||
| @pyqtSlot() | |||
| def slot_pluginsCenter(self): | |||
| @@ -1502,6 +1547,38 @@ class HostWindow(QMainWindow): | |||
| pitem.getWidget().setInternalParameter(PARAMETER_BALANCE_RIGHT, 1.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() | |||
| def slot_pluginsCompact(self): | |||
| for pitem in self.fPluginList: | |||
| @@ -1531,11 +1608,24 @@ class HostWindow(QMainWindow): | |||
| self.fPluginList.append(pitem) | |||
| self.fPluginCount += 1 | |||
| self.ui.act_plugin_remove_all.setEnabled(self.fPluginCount > 0) | |||
| self.setMenuMacrosEnabled(self.fPluginCount > 0) | |||
| if pluginType == PLUGIN_LV2: | |||
| 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) | |||
| def slot_handlePluginRemovedCallback(self, pluginId): | |||
| if self.fWithCanvas: | |||
| @@ -1558,7 +1648,8 @@ class HostWindow(QMainWindow): | |||
| del pitem | |||
| 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: | |||
| self.fCurrentlyRemovingAllPlugins = False | |||
| self.projectLoadingFinished(False) | |||
| @@ -1569,7 +1660,9 @@ class HostWindow(QMainWindow): | |||
| pitem = self.fPluginList[i] | |||
| pitem.setPluginId(i) | |||
| self.ui.act_plugin_remove_all.setEnabled(True) | |||
| # self.ui.act_plugin_remove_all.setEnabled(True) | |||
| self.setMenuMacrosEnabled(True) | |||
| # -------------------------------------------------------------------------------------------------------- | |||
| # Canvas | |||
| @@ -1975,6 +2068,7 @@ class HostWindow(QMainWindow): | |||
| settings.setValue("Geometry", self.saveGeometry()) | |||
| 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()) | |||
| diskFolders = [] | |||
| @@ -2009,11 +2103,24 @@ class HostWindow(QMainWindow): | |||
| showToolbar = settings.value("ShowToolbar", True, bool) | |||
| 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.setEnabled(showToolbar) | |||
| self.ui.toolBar.setVisible(showToolbar) | |||
| 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"): | |||
| #self.ui.splitter.restoreState(settings.value("SplitterState", b"")) | |||
| #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_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_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_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), | |||
| @@ -2182,6 +2290,19 @@ class HostWindow(QMainWindow): | |||
| self.ui.toolBar.blockSignals(True) | |||
| self.ui.toolBar.setEnabled(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) | |||
| @pyqtSlot(bool) | |||
| @@ -2623,6 +2744,8 @@ class HostWindow(QMainWindow): | |||
| self.ui.toolBar.setEnabled(visible) | |||
| self.ui.toolBar.blockSignals(False) | |||
| self.ui.act_settings_show_toolbar.setChecked(visible) | |||
| self.ui.act_settings_show_toolbar_text.setEnabled(visible) | |||
| @pyqtSlot(int) | |||
| def slot_tabChanged(self, index): | |||
| @@ -2889,7 +3012,6 @@ class HostWindow(QMainWindow): | |||
| def idleFast(self): | |||
| self.host.engine_idle() | |||
| self.refreshTransport() | |||
| if self.fPluginCount == 0 or self.fCurrentlyRemovingAllPlugins: | |||
| return | |||
| @@ -2922,6 +3044,10 @@ class HostWindow(QMainWindow): | |||
| def idleSlow(self): | |||
| 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: | |||
| return | |||
| @@ -2949,6 +3075,13 @@ class HostWindow(QMainWindow): | |||
| QMainWindow.changeEvent(self, event) | |||
| 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_imgL = QImage(":/bitmaps/rack_padding_left.png") | |||
| rack_imgR = QImage(":/bitmaps/rack_padding_right.png") | |||
| @@ -47,6 +47,7 @@ from carla_backend import ( | |||
| from carla_shared import ( | |||
| CARLA_KEY_MAIN_PROJECT_FOLDER, | |||
| CARLA_KEY_MAIN_USE_PRO_THEME, | |||
| CARLA_KEY_MAIN_SKIN_TWEAKS, | |||
| CARLA_KEY_MAIN_PRO_THEME_COLOR, | |||
| CARLA_KEY_MAIN_REFRESH_INTERVAL, | |||
| CARLA_KEY_MAIN_CONFIRM_EXIT, | |||
| @@ -115,6 +116,7 @@ from carla_shared import ( | |||
| CARLA_DEFAULT_MAIN_CLASSIC_SKIN, | |||
| CARLA_DEFAULT_MAIN_SHOW_LOGS, | |||
| CARLA_DEFAULT_MAIN_SYSTEM_ICONS, | |||
| CARLA_DEFAULT_MAIN_SKIN_TWEAKS, | |||
| #CARLA_DEFAULT_MAIN_EXPERIMENTAL, | |||
| CARLA_DEFAULT_CANVAS_THEME, | |||
| 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, | |||
| 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( | |||
| 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_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_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_REFRESH_INTERVAL, self.ui.sb_main_refresh_interval.value()) | |||
| 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.cb_main_theme_color.setCurrentIndex( | |||
| 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.ch_main_confirm_exit.setChecked(CARLA_DEFAULT_MAIN_CONFIRM_EXIT) | |||
| self.ui.cb_main_classic_skin_default(CARLA_DEFAULT_MAIN_CLASSIC_SKIN) | |||
| @@ -8,7 +8,7 @@ | |||
| import os | |||
| import sys | |||
| from math import fmod | |||
| from math import fmod, log10 | |||
| # ------------------------------------------------------------------------------------------------------------ | |||
| # Imports (Signal) | |||
| @@ -63,6 +63,9 @@ from carla_backend import ( | |||
| ENGINE_TRANSPORT_MODE_JACK, | |||
| ) | |||
| from utils import QSafeSettings | |||
| import ast | |||
| # ------------------------------------------------------------------------------------------------------------ | |||
| # Config | |||
| @@ -184,6 +187,7 @@ CANVAS_EYECANDY_SMALL = 1 | |||
| CARLA_KEY_MAIN_PROJECT_FOLDER = "Main/ProjectFolder" # str | |||
| CARLA_KEY_MAIN_USE_PRO_THEME = "Main/UseProTheme" # bool | |||
| 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_CONFIRM_EXIT = "Main/ConfirmExit" # 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_SYSTEM_ICONS = 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 | |||
| CARLA_DEFAULT_CANVAS_THEME = "Modern Dark" | |||
| @@ -647,18 +652,22 @@ else: | |||
| # Find decimal points for a parameter, using step and stepSmall | |||
| def countDecimalPoints(step, stepSmall): | |||
| if stepSmall >= 1.0: | |||
| if (stepSmall >= 1.0) or (step <= 0) or (stepSmall <= 0): | |||
| return 0 | |||
| if step >= 1.0: | |||
| 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) | |||
| @@ -927,5 +936,50 @@ def CustomMessageBox(parent, icon, title, text, | |||
| # pylint: enable=no-value-for-parameter | |||
| # 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 | |||
| @@ -6,15 +6,19 @@ | |||
| # Imports (Global) | |||
| from qt_compat import qt_config | |||
| import math | |||
| import random | |||
| import operator | |||
| from operator import itemgetter | |||
| if qt_config == 5: | |||
| from PyQt5.QtCore import Qt, QRectF, QLineF, QTimer | |||
| 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: | |||
| from PyQt6.QtCore import Qt, QRectF, QLineF, QTimer | |||
| 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) | |||
| @@ -29,7 +33,6 @@ from carla_backend import * | |||
| from carla_shared import * | |||
| from carla_widgets import * | |||
| from widgets.digitalpeakmeter import DigitalPeakMeter | |||
| from widgets.paramspinbox import CustomInputDialog | |||
| from widgets.scalabledial import ScalableDial | |||
| # ------------------------------------------------------------------------------------------------------------ | |||
| @@ -126,7 +129,7 @@ def getParameterShortName(paramName): | |||
| # Get RGB colors for a plugin category | |||
| def getColorFromCategory(category): | |||
| r = 40 | |||
| r = 39 | |||
| g = 40 | |||
| b = 40 | |||
| @@ -152,45 +155,35 @@ def getColorFromCategory(category): | |||
| 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 | |||
| @@ -235,6 +228,16 @@ class AbstractPluginSlot(QFrame, PluginEditParentMeta): | |||
| 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 | |||
| self.fIdleTimerId = 0 | |||
| @@ -269,6 +272,8 @@ class AbstractPluginSlot(QFrame, PluginEditParentMeta): | |||
| self.w_knobs_right = None | |||
| self.spacer_knobs = None | |||
| self.slowTimer = 0 | |||
| # ------------------------------------------------------------- | |||
| # Set-up connections | |||
| @@ -348,8 +353,31 @@ class AbstractPluginSlot(QFrame, PluginEditParentMeta): | |||
| if self.fEditDialog is not None and self.fPluginId == pluginId: | |||
| 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): | |||
| 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) | |||
| elif self.fSkinStyle.startswith("openav") or self.fSkinStyle == "zynfx": | |||
| 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): | |||
| self.peak_in.hide() | |||
| @@ -496,6 +527,9 @@ class AbstractPluginSlot(QFrame, PluginEditParentMeta): | |||
| self.peak_out.setMeterStyle(DigitalPeakMeter.STYLE_RNCBC) | |||
| elif self.fSkinStyle.startswith("openav") or self.fSkinStyle == "zynfx": | |||
| 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): | |||
| self.peak_out.hide() | |||
| @@ -530,7 +564,8 @@ class AbstractPluginSlot(QFrame, PluginEditParentMeta): | |||
| styleSheet2 = "background-image: url(:/bitmaps/background_%s.png);" % self.fSkinStyle | |||
| else: | |||
| 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: | |||
| colorEnabled = "#111" | |||
| @@ -551,12 +586,63 @@ class AbstractPluginSlot(QFrame, PluginEditParentMeta): | |||
| styleSheet += """ | |||
| QComboBox#cb_presets, | |||
| QComboBox#cb_presets0, | |||
| QComboBox#cb_presets1, | |||
| QLabel#label_audio_in, | |||
| QLabel#label_audio_out, | |||
| QLabel#label_midi { font-size: 10px; } | |||
| """ | |||
| 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 | |||
| @@ -565,6 +651,11 @@ class AbstractPluginSlot(QFrame, PluginEditParentMeta): | |||
| index = 0 | |||
| 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): | |||
| # 50 should be enough for everybody, right? | |||
| if index >= 50: | |||
| @@ -573,64 +664,145 @@ class AbstractPluginSlot(QFrame, PluginEditParentMeta): | |||
| paramInfo = self.host.get_parameter_info(self.fPluginId, i) | |||
| paramData = self.host.get_parameter_data(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"): | |||
| print("Carla: INFO: Parameter "+str(i)+" is Unused, so skipped.") | |||
| 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() | |||
| 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 | |||
| self.fParameterList.append([i, 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: | |||
| 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.setValue(self.host.get_internal_parameter_value(self.fPluginId, paramIndex)) | |||
| paramWidget.blockSignals(False) | |||
| @@ -724,6 +896,7 @@ class AbstractPluginSlot(QFrame, PluginEditParentMeta): | |||
| elif parameterId == PARAMETER_CTRL_CHANNEL: | |||
| self.host.set_ctrl_channel(self.fPluginId, value) | |||
| self.setParameterValue(parameterId, value, True) | |||
| self.fEditDialog.setParameterValue(parameterId, value) | |||
| # ----------------------------------------------------------------- | |||
| @@ -891,13 +1064,18 @@ class AbstractPluginSlot(QFrame, PluginEditParentMeta): | |||
| paramWidget.setVisible(hints & PLUGIN_CAN_DRYWET) | |||
| elif paramIndex == PARAMETER_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: | |||
| self.b_gui.setEnabled(bool(hints & PLUGIN_HAS_CUSTOM_UI)) | |||
| # NOTE: self.fParameterList is empty when compacted. | |||
| def editDialogParameterValueChanged(self, pluginId, parameterId, value): | |||
| for paramIndex, paramWidget in self.fParameterList: | |||
| if paramIndex != parameterId: | |||
| if (paramIndex != parameterId) or paramWidget.fIsOutput: | |||
| continue | |||
| paramWidget.blockSignals(True) | |||
| @@ -905,6 +1083,16 @@ class AbstractPluginSlot(QFrame, PluginEditParentMeta): | |||
| paramWidget.blockSignals(False) | |||
| 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): | |||
| if self.cb_presets is None: | |||
| return | |||
| @@ -997,6 +1185,23 @@ class AbstractPluginSlot(QFrame, PluginEditParentMeta): | |||
| 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): | |||
| @@ -1023,7 +1228,9 @@ class AbstractPluginSlot(QFrame, PluginEditParentMeta): | |||
| def updateParameterValues(self): | |||
| for paramIndex, paramWidget in self.fParameterList: | |||
| if paramIndex < 0: | |||
| if paramIndex < 0: # DryWet and Volume | |||
| continue | |||
| if paramWidget.fIsOutput: | |||
| continue | |||
| paramWidget.blockSignals(True) | |||
| @@ -1044,8 +1251,9 @@ class AbstractPluginSlot(QFrame, PluginEditParentMeta): | |||
| # Expand/Minimize and Tweaks | |||
| 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() | |||
| # ------------------------------------------------------------- | |||
| @@ -1157,28 +1365,17 @@ class AbstractPluginSlot(QFrame, PluginEditParentMeta): | |||
| colorStr = "%i;%i;%i" % color | |||
| 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"), | |||
| self.tr("Change Skin to:"), | |||
| skinList, index, False) | |||
| skinList, arrayIndex(skinList, self.fSkinStyle), False) | |||
| if not all(skin): | |||
| return | |||
| gCarla.gui.changePluginSkin(self.fPluginId, skin[0]) | |||
| @@ -1287,97 +1484,7 @@ class AbstractPluginSlot(QFrame, PluginEditParentMeta): | |||
| @pyqtSlot() | |||
| 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: | |||
| 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(): | |||
| widget.show() | |||
| continue | |||
| @@ -1719,6 +1840,12 @@ class PluginSlot_Compact(AbstractPluginSlot): | |||
| self.peak_in = self.ui.peak_in | |||
| 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() | |||
| # ----------------------------------------------------------------- | |||
| @@ -1756,11 +1883,24 @@ class PluginSlot_Default(AbstractPluginSlot): | |||
| self.w_knobs_right = self.ui.w_knobs_right | |||
| 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() | |||
| # ----------------------------------------------------------------- | |||
| def getFixedHeight(self): | |||
| if self.fSkinStyle == "tube": | |||
| return 98 | |||
| return 80 | |||
| # ----------------------------------------------------------------- | |||
| @@ -1839,13 +1979,17 @@ class PluginSlot_Presets(AbstractPluginSlot): | |||
| self.peak_in = self.ui.peak_in | |||
| 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() | |||
| else: | |||
| self.w_knobs_left = self.ui.w_knobs_left | |||
| self.w_knobs_right = self.ui.w_knobs_right | |||
| 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() | |||
| 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): | |||
| 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): | |||
| pluginInfo = host.get_plugin_info(pluginId) | |||
| pluginName = host.get_real_plugin_name(pluginId) | |||
| @@ -2018,9 +2204,9 @@ def getColorAndSkinStyle(host, pluginId): | |||
| # Samplers | |||
| if pluginInfo['type'] == PLUGIN_SF2: | |||
| return (colorCategory, "sf2") | |||
| return (colorCategory, "mpresets") | |||
| if pluginInfo['type'] == PLUGIN_SFZ: | |||
| return (colorCategory, "sfz") | |||
| return (colorCategory, "mpresets") | |||
| # Calf | |||
| if pluginName.split(" ", 1)[0].lower() == "calf": | |||
| @@ -2032,6 +2218,10 @@ def getColorAndSkinStyle(host, pluginId): | |||
| if pluginMaker == "OpenAV": | |||
| return (colorNone, "openav") | |||
| # Tube | |||
| if "tube" in pluginLabel: | |||
| return (colorCategory, "tube") | |||
| # ZynFX | |||
| if pluginInfo['type'] == PLUGIN_INTERNAL: | |||
| if pluginLabel.startswith("zyn") and pluginInfo['category'] != PLUGIN_CATEGORY_SYNTH: | |||
| @@ -2042,7 +2232,8 @@ def getColorAndSkinStyle(host, pluginId): | |||
| return (colorNone, "zynfx") | |||
| 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") | |||
| # Presets | |||
| @@ -14,7 +14,7 @@ from qt_compat import qt_config | |||
| if qt_config == 5: | |||
| 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 ( | |||
| QDialog, | |||
| QFileDialog, | |||
| @@ -24,10 +24,18 @@ if qt_config == 5: | |||
| QScrollArea, | |||
| QVBoxLayout, | |||
| QWidget, | |||
| QGraphicsScene, | |||
| QGraphicsTextItem, | |||
| QGraphicsView, | |||
| QTableWidget, | |||
| QTableWidgetItem, | |||
| QHeaderView, | |||
| QLabel, | |||
| QSizePolicy, | |||
| ) | |||
| elif qt_config == 6: | |||
| 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 ( | |||
| QDialog, | |||
| QFileDialog, | |||
| @@ -37,6 +45,14 @@ elif qt_config == 6: | |||
| QScrollArea, | |||
| QVBoxLayout, | |||
| 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_PROGRAM_CHANGES, | |||
| PLUGIN_OPTION_SKIP_SENDING_NOTES, | |||
| PARAMETER_NULL, | |||
| PARAMETER_DRYWET, | |||
| PARAMETER_VOLUME, | |||
| PARAMETER_BALANCE_LEFT, | |||
| @@ -84,6 +101,7 @@ from carla_backend import ( | |||
| PARAMETER_USES_SCALEPOINTS, | |||
| PARAMETER_USES_CUSTOM_TEXT, | |||
| PARAMETER_CAN_BE_CV_CONTROLLED, | |||
| parameterHintsText, | |||
| PARAMETER_INPUT, PARAMETER_OUTPUT, | |||
| CONTROL_INDEX_NONE, | |||
| CONTROL_INDEX_MIDI_PITCHBEND, | |||
| @@ -94,6 +112,7 @@ from carla_backend import ( | |||
| from carla_shared import ( | |||
| MIDI_CC_LIST, MAX_MIDI_CC_LIST_ITEM, | |||
| countDecimalPoints, | |||
| strLim, | |||
| fontMetricsHorizontalAdvance, | |||
| setUpSignals, | |||
| gCarla | |||
| @@ -103,6 +122,8 @@ from carla_utils import getPluginTypeAsString | |||
| from widgets.collapsablewidget import CollapsibleBox | |||
| from widgets.pixmapkeyboard import PixmapKeyboardHArea | |||
| from widgets.paramspinbox import CustomInputDialog, ParamSpinBox | |||
| from widgets.scalabledial import ScalableDial | |||
| # ------------------------------------------------------------------------------------------------------------ | |||
| # Carla GUI defines | |||
| @@ -119,6 +140,7 @@ class PluginParameter(QWidget): | |||
| mappedControlChanged = pyqtSignal(int, int) | |||
| mappedRangeChanged = pyqtSignal(int, float, float) | |||
| midiChannelChanged = pyqtSignal(int, int) | |||
| knobVisibilityChanged = pyqtSignal(int, int) | |||
| valueChanged = pyqtSignal(int, float) | |||
| def __init__(self, parent, host, pInfo, pluginId, tabIndex): | |||
| @@ -141,6 +163,7 @@ class PluginParameter(QWidget): | |||
| self.fParameterId = pInfo['index'] | |||
| self.fPluginId = pluginId | |||
| self.fTabIndex = tabIndex | |||
| self.fKnobVisible = True | |||
| # ------------------------------------------------------------- | |||
| # Set-up GUI | |||
| @@ -157,6 +180,7 @@ class PluginParameter(QWidget): | |||
| self.ui.widget.setStep(pInfo['step']) | |||
| self.ui.widget.setStepSmall(pInfo['stepSmall']) | |||
| self.ui.widget.setStepLarge(pInfo['stepLarge']) | |||
| # NOTE: Issue #1983 | |||
| self.ui.widget.setScalePoints(pInfo['scalePoints'], bool(pHints & PARAMETER_USES_SCALEPOINTS)) | |||
| if pInfo['comment']: | |||
| @@ -536,39 +560,21 @@ class PluginEdit(QDialog): | |||
| labelPluginFont.setWeight(75) | |||
| 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_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_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_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_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.sb_ctrl_channel.setValue(self.fControlChannel+1) | |||
| @@ -582,10 +588,10 @@ class PluginEdit(QDialog): | |||
| self.ui.scrollArea.setVisible(False) | |||
| # 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 &= ~Qt.WindowContextHelpButtonHint | |||
| @@ -901,7 +907,6 @@ class PluginEdit(QDialog): | |||
| break | |||
| paramData = self.host.get_parameter_data(self.fPluginId, i) | |||
| if paramData['type'] not in (PARAMETER_INPUT, PARAMETER_OUTPUT): | |||
| unusedParameters += 1 | |||
| continue | |||
| @@ -974,6 +979,12 @@ class PluginEdit(QDialog): | |||
| self._createParameterWidgets(PARAMETER_INPUT, paramInputListFull, self.tr("Parameters")) | |||
| 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 | |||
| if tabIndex < self.ui.tabWidget.count(): | |||
| self.ui.tabWidget.setCurrentIndex(tabIndex) | |||
| @@ -1474,69 +1485,67 @@ class PluginEdit(QDialog): | |||
| @pyqtSlot() | |||
| 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: | |||
| 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) | |||
| actReset = menu.addAction(self.tr("Reset (%i%%)" % (default*100))) | |||
| actReset = menu.addAction(textReset) | |||
| 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() | |||
| 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) | |||
| actSelected = menu.exec_(QCursor.pos()) | |||
| 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 | |||
| value = dialog.returnValue() / percent | |||
| elif actSelected == actMinimum: | |||
| value = minimum | |||
| elif actSelected == actMaximum: | |||
| @@ -1605,13 +1614,15 @@ class PluginEdit(QDialog): | |||
| scrollAreaLayout = QVBoxLayout(scrollAreaWidget) | |||
| scrollAreaLayout.setSpacing(3) | |||
| expandBox = (len(paramList) < 50) | |||
| for paramInfo in paramList: | |||
| groupName = paramInfo['groupName'] | |||
| if groupName: | |||
| groupSymbol, groupName = groupName.split(":",1) | |||
| groupLayout, groupWidget = groupWidgets.get(groupSymbol, (None, None)) | |||
| if groupLayout is None: | |||
| groupWidget = CollapsibleBox(groupName, scrollAreaWidget) | |||
| groupWidget = CollapsibleBox(groupName, scrollAreaWidget, expandBox) | |||
| groupLayout = groupWidget.getContentLayout() | |||
| groupWidget.setPalette(palette2) | |||
| 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): | |||
| self.fIdleTimerId = self.startTimer(50) | |||
| @@ -33,7 +33,7 @@ class QToolButtonWithMouseTracking(QToolButton): | |||
| QToolButton.leaveEvent(self, event) | |||
| class CollapsibleBox(QFrame): | |||
| def __init__(self, title, parent): | |||
| def __init__(self, title, parent, startsExpanded = True): | |||
| QFrame.__init__(self, parent) | |||
| self.setFrameShape(QFrame.StyledPanel) | |||
| @@ -42,10 +42,10 @@ class CollapsibleBox(QFrame): | |||
| self.toggle_button = QToolButtonWithMouseTracking(self) | |||
| self.toggle_button.setText(title) | |||
| 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.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.content_area = QWidget(self) | |||
| @@ -59,6 +59,8 @@ class CollapsibleBox(QFrame): | |||
| lay.addWidget(self.toggle_button) | |||
| lay.addWidget(self.content_area) | |||
| self.toolButtonPressed(startsExpanded) # Set initial state | |||
| @pyqtSlot(bool) | |||
| def toolButtonPressed(self, toggled): | |||
| self.content_area.setVisible(toggled) | |||
| @@ -1,22 +1,26 @@ | |||
| #!/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 | |||
| # --------------------------------------------------------------------------------------------------------------------- | |||
| # Imports (Global) | |||
| from math import isnan | |||
| from math import isnan, log10 | |||
| from qt_compat import qt_config | |||
| 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.QtWidgets import QDial | |||
| from PyQt5.QtWidgets import QWidget, QToolTip, QInputDialog | |||
| 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.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 | |||
| @@ -25,7 +29,7 @@ elif qt_config == 6: | |||
| #def updateSizes(self): | |||
| #def paintDial(self, painter): | |||
| class CommonDial(QDial): | |||
| class CommonDial(QWidget): | |||
| # enum CustomPaintMode | |||
| 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) | |||
| @@ -33,9 +37,11 @@ class CommonDial(QDial): | |||
| CUSTOM_PAINT_MODE_CARLA_L = 3 # 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_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_CARLA_WET_MINI = 9 # for compacted slot | |||
| CUSTOM_PAINT_MODE_CARLA_VOL_MINI = 10 # for compacted slot | |||
| # enum Orientation | |||
| HORIZONTAL = 0 | |||
| @@ -51,16 +57,37 @@ class CommonDial(QDial): | |||
| dragStateChanged = pyqtSignal(bool) | |||
| 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.fMinimum = 0.0 | |||
| self.fMaximum = 1.0 | |||
| self.fLabel = label | |||
| self.fLastLabel = "" | |||
| 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.fIsPressed = False | |||
| @@ -69,9 +96,6 @@ class CommonDial(QDial): | |||
| self.fLastDragPos = None | |||
| self.fLastDragValue = 0.0 | |||
| self.fIndex = index | |||
| self.fLabel = "" | |||
| self.fLabelPos = QPointF(0.0, 0.0) | |||
| self.fLabelFont = QFont(self.font()) | |||
| self.fLabelFont.setPixelSize(8) | |||
| @@ -97,75 +121,79 @@ class CommonDial(QDial): | |||
| self.fLabelGradientRect = QRectF(0.0, 0.0, 0.0, 0.0) | |||
| self.fCustomPaintMode = self.CUSTOM_PAINT_MODE_NULL | |||
| 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): | |||
| self.fLabelGradientColor1 = QColor(0, 0, 0, 255) | |||
| self.fLabelGradientColor2 = QColor(0, 0, 0, 0) | |||
| 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): | |||
| 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): | |||
| 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): | |||
| if self.fRealValue == value or isnan(value): | |||
| return | |||
| if value <= self.fMinimum: | |||
| qtValue = 0 | |||
| if (not self.fIsOutput) and value <= 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 | |||
| elif self.fIsInteger or (abs(value - int(value)) < 1e-8): # tiny "notch" | |||
| self.fRealValue = round(value) | |||
| else: | |||
| qtValue = round(float(value - self.fMinimum) / float(self.fMaximum - self.fMinimum) * self.fPrecision) | |||
| self.fRealValue = value | |||
| # Block change signal, we'll handle it ourselves | |||
| self.blockSignals(True) | |||
| QDial.setValue(self, qtValue) | |||
| self.blockSignals(False) | |||
| if emitSignal: | |||
| self.realValueChanged.emit(self.fRealValue) | |||
| def setCustomPaintMode(self, paintMode): | |||
| if self.fCustomPaintMode == paintMode: | |||
| return | |||
| self.fCustomPaintMode = paintMode | |||
| self.update() | |||
| def setCustomPaintColor(self, color): | |||
| @@ -173,76 +201,262 @@ class CommonDial(QDial): | |||
| return | |||
| self.fCustomPaintColor = color | |||
| self.updateSizes() | |||
| 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) | |||
| def slot_valueChanged(self, value): | |||
| self.fRealValue = float(value)/self.fPrecision * (self.fMaximum - self.fMinimum) + self.fMinimum | |||
| 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): | |||
| self.setFocus() | |||
| self.fIsHovered = True | |||
| if self.fHoverStep == self.HOVER_MIN: | |||
| self.fHoverStep = self.HOVER_MIN + 1 | |||
| QDial.enterEvent(self, event) | |||
| self.update() | |||
| def leaveEvent(self, event): | |||
| self.fIsHovered = False | |||
| if self.fHoverStep == self.HOVER_MAX: | |||
| 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): | |||
| if self.fDialMode == self.MODE_DEFAULT: | |||
| QDial.mousePressEvent(self, event) | |||
| if self.fDialMode == self.MODE_DEFAULT or self.fIsOutput: | |||
| return | |||
| 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): | |||
| if self.fDialMode == self.MODE_DEFAULT: | |||
| QDial.mouseMoveEvent(self, event) | |||
| if self.fDialMode == self.MODE_DEFAULT or self.fIsOutput: | |||
| return | |||
| if not self.fIsPressed: | |||
| 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): | |||
| if self.fDialMode == self.MODE_DEFAULT: | |||
| QDial.mouseReleaseEvent(self, event) | |||
| if self.fDialMode == self.MODE_DEFAULT or self.fIsOutput: | |||
| return | |||
| if self.fIsPressed: | |||
| self.fIsPressed = 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): | |||
| painter = QPainter(self) | |||
| event.accept() | |||
| @@ -250,22 +464,118 @@ class CommonDial(QDial): | |||
| painter.save() | |||
| 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.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.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) | |||
| 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() | |||
| 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_RNCBC = 3 | |||
| STYLE_CALF = 4 | |||
| STYLE_TUBE = 5 | |||
| # ----------------------------------------------------------------------------------------------------------------- | |||
| @@ -153,7 +154,7 @@ class DigitalPeakMeter(QWidget): | |||
| if self.fMeterStyle == style: | |||
| 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") | |||
| return | |||
| @@ -163,7 +164,7 @@ class DigitalPeakMeter(QWidget): | |||
| self.fMeterBackground = QColor("#1A1A1A") | |||
| elif style == self.STYLE_RNCBC: | |||
| self.fMeterBackground = QColor("#070707") | |||
| elif style == self.STYLE_CALF: | |||
| elif style in (self.STYLE_CALF, self.STYLE_TUBE): | |||
| self.fMeterBackground = QColor("#000") | |||
| if style == self.STYLE_CALF: | |||
| @@ -215,23 +216,36 @@ class DigitalPeakMeter(QWidget): | |||
| i = meter - 1 | |||
| if level < 0.001: | |||
| level = 0.0 | |||
| elif level > 0.999: | |||
| level = 1.0 | |||
| if self.fSmoothMultiplier > 0 and not forced: | |||
| level = ( | |||
| (self.fLastChannelData[i] * float(self.fSmoothMultiplier) + level) | |||
| / 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: | |||
| self.fChannelData[i] = level | |||
| self.update() | |||
| self.fLastChannelData[i] = level | |||
| # ----------------------------------------------------------------------------------------------------------------- | |||
| def updateGrandient(self): | |||
| @@ -284,6 +298,14 @@ class DigitalPeakMeter(QWidget): | |||
| self.fMeterGradient.setColorAt(0.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() | |||
| def updateGrandientFinalStop(self): | |||
| @@ -360,6 +382,13 @@ class DigitalPeakMeter(QWidget): | |||
| meterPad += 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: | |||
| painter.setPen(QPen(self.fMeterBackground, 0)) | |||
| painter.setBrush(self.fMeterGradient) | |||
| @@ -25,7 +25,7 @@ elif qt_config == 6: | |||
| import ui_inputdialog_value | |||
| 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 | |||
| @@ -46,13 +46,21 @@ def geFixedValue(name, value, minimum, maximum): | |||
| # Custom InputDialog with Scale Points support | |||
| 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) | |||
| self.ui = ui_inputdialog_value.Ui_Dialog() | |||
| 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.setRange(minimum, maximum) | |||
| self.ui.doubleSpinBox.setSingleStep(step) | |||
| @@ -361,14 +369,7 @@ class ParamSpinBox(QAbstractSpinBox): | |||
| self.fStepLarge = value | |||
| def setLabel(self, label): | |||
| prefix = "" | |||
| suffix = label.strip() | |||
| if suffix == "(coef)": | |||
| prefix = "* " | |||
| suffix = "" | |||
| else: | |||
| suffix = " " + suffix | |||
| prefix, suffix = getPrefixSuffix(label) | |||
| self.fLabelPrefix = prefix | |||
| self.fLabelSuffix = suffix | |||
| @@ -533,16 +534,16 @@ class ParamSpinBox(QAbstractSpinBox): | |||
| pass | |||
| 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")) | |||
| menu.addSeparator() | |||
| actCopy = menu.addAction(self.tr("Copy (%f)" % self.fValue)) | |||
| actCopy = menu.addAction(self.tr("Copy (" + strLim(self.fValue) + ")")) | |||
| if pasteValue is None: | |||
| actPaste = menu.addAction(self.tr("Paste")) | |||
| actPaste.setEnabled(False) | |||
| else: | |||
| actPaste = menu.addAction(self.tr("Paste (%f)" % pasteValue)) | |||
| actPaste = menu.addAction(self.tr("Paste (" + strLim(pasteValue) + ")")) | |||
| menu.addSeparator() | |||
| @@ -13,11 +13,11 @@ import os | |||
| from qt_compat import qt_config | |||
| 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.QtWidgets import QAbstractItemView, QListWidget, QListWidgetItem, QMessageBox | |||
| 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.QtWidgets import QAbstractItemView, QListWidget, QListWidgetItem, QMessageBox | |||
| @@ -291,7 +291,10 @@ class RackListWidget(QListWidget): | |||
| 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: | |||
| self.setCurrentRow(tryItem.getPluginId()) | |||
| @@ -318,7 +321,10 @@ class RackListWidget(QListWidget): | |||
| if not urls: | |||
| 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: | |||
| pluginId = tryItem.getPluginId() | |||
| @@ -10,17 +10,21 @@ from qt_compat import qt_config | |||
| if qt_config == 5: | |||
| from PyQt5.QtCore import pyqtSignal, pyqtSlot, QT_VERSION, Qt, QPointF, QRectF, QSize, QTimer | |||
| 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: | |||
| from PyQt6.QtCore import pyqtSignal, pyqtSlot, QT_VERSION, Qt, QPointF, QRectF, QSize, QTimer | |||
| 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) | |||
| from carla_shared import * | |||
| from carla_utils import * | |||
| from widgets.scalabledial import ScalableDial | |||
| import ui_xycontroller | |||
| @@ -32,15 +36,21 @@ from externalui import ExternalUI | |||
| 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): | |||
| # signals | |||
| cursorMoved = pyqtSignal(float,float) | |||
| knobsUpdate = pyqtSignal(float,float) | |||
| def __init__(self, parent): | |||
| QGraphicsScene.__init__(self, parent) | |||
| @@ -52,9 +62,20 @@ class XYGraphicsScene(QGraphicsScene): | |||
| self.m_channels = [] | |||
| self.m_mouseLock = 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_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) | |||
| cursorPen = QPen(QColor(255, 255, 255), 2) | |||
| @@ -87,31 +108,31 @@ class XYGraphicsScene(QGraphicsScene): | |||
| self.m_lineV.setX(posX) | |||
| 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) | |||
| else: | |||
| self.m_smooth_x = posX; | |||
| self.m_smooth_x = posX | |||
| def setPosY(self, y: float, forward: bool = True): | |||
| 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_lineH.setY(posY) | |||
| 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) | |||
| else: | |||
| self.m_smooth_y = posY | |||
| def setSmooth(self, smooth: bool): | |||
| self.m_smooth = smooth | |||
| 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), | |||
| -(float(size.height())/2), | |||
| 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 | |||
| 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 | |||
| 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() | |||
| 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() | |||
| same += 1 | |||
| if same == 2: | |||
| 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 pos.x() < self.p_size.x(): | |||
| pos.setX(self.p_size.x()) | |||
| 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(): | |||
| pos.setY(self.p_size.y()) | |||
| elif pos.y() > (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_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 | |||
| msgd = ["cc2" if xp is not None and yp is not None else "cc"] | |||
| prefix = [] | |||
| msgd = [] | |||
| 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: | |||
| 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): | |||
| self.m_mouseLock = True | |||
| self.handleMousePos(event.scenePos()) | |||
| self.handleMousePos(event) | |||
| self.rparent.setCursor(Qt.CrossCursor) | |||
| QGraphicsScene.mousePressEvent(self, event); | |||
| QGraphicsScene.mousePressEvent(self, event) | |||
| 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): | |||
| self.m_mouseLock = False | |||
| @@ -231,15 +302,22 @@ class XYControllerUI(ExternalUI, QMainWindow): | |||
| self.fSaveSizeNowChecker = -1 | |||
| self.isXActual = False | |||
| self.isYActual = False | |||
| # --------------------------------------------------------------- | |||
| # Set-up GUI stuff | |||
| 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.graphicsView.setScene(self.scene) | |||
| @@ -262,6 +340,7 @@ class XYControllerUI(ExternalUI, QMainWindow): | |||
| # Connect actions to functions | |||
| 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.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_y.realValueChanged.connect(self.slot_knobValueChangedY) | |||
| if QT_VERSION >= 0x60000: | |||
| self.ui.cb_control_x.currentTextChanged.connect(self.slot_checkCC_X) | |||
| 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() | |||
| def slot_updateScreen(self): | |||
| self.ui.graphicsView.centerOn(0, 0) | |||
| 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) | |||
| def slot_noteOn(self, note): | |||
| self.send(["note", True, note]) | |||
| @@ -328,16 +414,34 @@ class XYControllerUI(ExternalUI, QMainWindow): | |||
| self.send(["note", False, note]) | |||
| @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) | |||
| 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) | |||
| def slot_checkCC_X(self, text: str): | |||
| @@ -422,20 +526,16 @@ class XYControllerUI(ExternalUI, QMainWindow): | |||
| @pyqtSlot(bool) | |||
| def slot_setSmooth(self, smooth): | |||
| self.scene.setSmooth(smooth) | |||
| self.setSmooth(smooth) | |||
| 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) | |||
| 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) | |||
| def slot_showKeyboard(self, yesno): | |||
| @@ -443,21 +543,47 @@ class XYControllerUI(ExternalUI, QMainWindow): | |||
| self.sendConfigure("show-midi-keyboard", "yes" if yesno else "no") | |||
| 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 | |||
| # NOTE It called continuously with params 6, 7 (outs, not used here), is this good? | |||
| 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: | |||
| 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: | |||
| return | |||
| self.scene.setSmoothValues(self.ui.dial_x.rvalue() / 100, | |||
| self.ui.dial_y.rvalue() / 100) | |||
| def dspStateChanged(self, key: str, value: str): | |||
| if key == "guiWidth": | |||
| @@ -480,15 +606,7 @@ class XYControllerUI(ExternalUI, QMainWindow): | |||
| elif key == "smooth": | |||
| 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": | |||
| show = (value == "yes") | |||
| @@ -587,9 +705,10 @@ class XYControllerUI(ExternalUI, QMainWindow): | |||
| QMainWindow.resizeEvent(self, event) | |||
| def timerEvent(self, event): | |||
| self.scene.time += 1 | |||
| if event.timerId() == self.fIdleTimer: | |||
| self.idleExternalUI() | |||
| self.scene.updateSmooth() | |||
| self.scene.updateSmooth(self.scene.time) | |||
| if self.fSaveSizeNowChecker == 11: | |||
| self.sendConfigure("guiWidth", str(self.width())) | |||
| @@ -29,6 +29,10 @@ class XYControllerPlugin : public NativePluginAndUiClass | |||
| { | |||
| public: | |||
| enum Parameters { | |||
| kParamSmooth, | |||
| kParamLinear, | |||
| kParamSpeed, | |||
| kParamReverseY, | |||
| kParamInX, | |||
| kParamInY, | |||
| kParamOutX, | |||
| @@ -44,6 +48,7 @@ public: | |||
| mqueueRT() | |||
| { | |||
| carla_zeroStruct(params); | |||
| params[kParamSpeed] = 8.0f; | |||
| carla_zeroStruct(channels); | |||
| channels[0] = true; | |||
| } | |||
| @@ -92,6 +97,56 @@ protected: | |||
| hints |= NATIVE_PARAMETER_IS_OUTPUT; | |||
| param.name = "Out Y"; | |||
| 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); | |||
| @@ -113,8 +168,14 @@ protected: | |||
| { | |||
| switch (index) | |||
| { | |||
| case kParamSmooth: | |||
| case kParamLinear: | |||
| case kParamSpeed: | |||
| case kParamReverseY: | |||
| case kParamInX: | |||
| case kParamInY: | |||
| case kParamOutX: | |||
| case kParamOutY: | |||
| params[index] = value; | |||
| break; | |||
| } | |||
| @@ -147,8 +208,8 @@ protected: | |||
| void process(const float* const*, float**, const uint32_t, | |||
| 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)) | |||
| { | |||
| @@ -266,7 +327,7 @@ static const NativePluginDescriptor notesDesc = { | |||
| /* audioOuts */ 0, | |||
| /* midiIns */ 1, | |||
| /* midiOuts */ 1, | |||
| /* paramIns */ 2, | |||
| /* paramIns */ 6, | |||
| /* paramOuts */ 2, | |||
| /* name */ "XY Controller", | |||
| /* label */ "xycontroller", | |||