Browse Source

Merge 5e505428f3 into 12bc40fd6c

pull/2010/merge
Filipe Coelho GitHub 1 month ago
parent
commit
3d580b9ed9
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
44 changed files with 2890 additions and 929 deletions
  1. BIN
      resources/16x16/add-from-favorites.svgz
  2. BIN
      resources/16x16/add-jack-alt.svgz
  3. BIN
      resources/16x16/add-jack.svgz
  4. BIN
      resources/16x16/audio-volume-medium.svgz
  5. BIN
      resources/16x16/audio-volume-muted-orig.svgz
  6. BIN
      resources/16x16/audio-volume-muted.svgz
  7. BIN
      resources/16x16/balance-alt.svgz
  8. BIN
      resources/16x16/balance-alt2.svgz
  9. BIN
      resources/16x16/balance.svgz
  10. BIN
      resources/16x16/compact-alt.svgz
  11. BIN
      resources/16x16/compact.svgz
  12. BIN
      resources/16x16/dry.svgz
  13. BIN
      resources/16x16/emblem-favorite.svgz
  14. BIN
      resources/16x16/restore-alt.svgz
  15. BIN
      resources/16x16/restore.svgz
  16. BIN
      resources/16x16/style-alt.svgz
  17. BIN
      resources/16x16/style.svgz
  18. BIN
      resources/16x16/system-shutdown.svgz
  19. BIN
      resources/16x16/system-turnon.svgz
  20. BIN
      resources/16x16/view-refresh-purple.svgz
  21. BIN
      resources/16x16/wet.svgz
  22. +14
    -0
      resources/resources.qrc
  23. +101
    -149
      resources/ui/carla_edit.ui
  24. +99
    -1
      resources/ui/carla_host.ui
  25. +21
    -0
      resources/ui/carla_settings.ui
  26. +90
    -14
      resources/ui/xycontroller.ui
  27. +28
    -4
      source/backend/plugin/CarlaPluginFluidSynth.cpp
  28. +32
    -1
      source/backend/plugin/CarlaPluginLADSPADSSI.cpp
  29. +32
    -1
      source/backend/plugin/CarlaPluginLV2.cpp
  30. +33
    -2
      source/backend/plugin/CarlaPluginNative.cpp
  31. +10
    -0
      source/frontend/carla_backend.py
  32. +146
    -13
      source/frontend/carla_host.py
  33. +7
    -0
      source/frontend/carla_settings.py
  34. +62
    -8
      source/frontend/carla_shared.py
  35. +394
    -203
      source/frontend/carla_skin.py
  36. +227
    -82
      source/frontend/carla_widgets.py
  37. +5
    -3
      source/frontend/widgets/collapsablewidget.py
  38. +406
    -96
      source/frontend/widgets/commondial.py
  39. +37
    -8
      source/frontend/widgets/digitalpeakmeter.py
  40. +16
    -15
      source/frontend/widgets/paramspinbox.py
  41. +10
    -4
      source/frontend/widgets/racklistwidget.py
  42. +839
    -224
      source/frontend/widgets/scalabledial.py
  43. +217
    -98
      source/frontend/xycontroller-ui
  44. +64
    -3
      source/native-plugins/xycontroller.cpp

BIN
resources/16x16/add-from-favorites.svgz View File


BIN
resources/16x16/add-jack-alt.svgz View File


BIN
resources/16x16/add-jack.svgz View File


BIN
resources/16x16/audio-volume-medium.svgz View File


BIN
resources/16x16/audio-volume-muted-orig.svgz View File


BIN
resources/16x16/audio-volume-muted.svgz View File


BIN
resources/16x16/balance-alt.svgz View File


BIN
resources/16x16/balance-alt2.svgz View File


BIN
resources/16x16/balance.svgz View File


BIN
resources/16x16/compact-alt.svgz View File


BIN
resources/16x16/compact.svgz View File


BIN
resources/16x16/dry.svgz View File


BIN
resources/16x16/emblem-favorite.svgz View File


BIN
resources/16x16/restore-alt.svgz View File


BIN
resources/16x16/restore.svgz View File


BIN
resources/16x16/style-alt.svgz View File


BIN
resources/16x16/style.svgz View File


BIN
resources/16x16/system-shutdown.svgz View File


BIN
resources/16x16/system-turnon.svgz View File


BIN
resources/16x16/view-refresh-purple.svgz View File


BIN
resources/16x16/wet.svgz View File


+ 14
- 0
resources/resources.qrc View File

@@ -3,9 +3,15 @@
<file>16x16/carla.png</file> <file>16x16/carla.png</file>
<file>16x16/carla-control.png</file> <file>16x16/carla-control.png</file>


<file>16x16/add-from-favorites.svgz</file>
<file>16x16/add-jack.svgz</file>
<file>16x16/application-exit.svgz</file> <file>16x16/application-exit.svgz</file>
<file>16x16/arrow-right.svgz</file> <file>16x16/arrow-right.svgz</file>
<file>16x16/audio-volume-medium.svgz</file>
<file>16x16/audio-volume-muted.svgz</file>
<file>16x16/balance.svgz</file>
<file>16x16/bookmarks.svgz</file> <file>16x16/bookmarks.svgz</file>
<file>16x16/compact.svgz</file>
<file>16x16/configure.svgz</file> <file>16x16/configure.svgz</file>
<file>16x16/dialog-cancel.svgz</file> <file>16x16/dialog-cancel.svgz</file>
<file>16x16/dialog-error.svgz</file> <file>16x16/dialog-error.svgz</file>
@@ -16,9 +22,11 @@
<file>16x16/document-open.svgz</file> <file>16x16/document-open.svgz</file>
<file>16x16/document-save.svgz</file> <file>16x16/document-save.svgz</file>
<file>16x16/document-save-as.svgz</file> <file>16x16/document-save-as.svgz</file>
<file>16x16/dry.svgz</file>
<file>16x16/edit-clear.svgz</file> <file>16x16/edit-clear.svgz</file>
<file>16x16/edit-delete.svgz</file> <file>16x16/edit-delete.svgz</file>
<file>16x16/edit-rename.svgz</file> <file>16x16/edit-rename.svgz</file>
<file>16x16/emblem-favorite.svgz</file>
<file>16x16/list-add.svgz</file> <file>16x16/list-add.svgz</file>
<file>16x16/list-remove.svgz</file> <file>16x16/list-remove.svgz</file>
<file>16x16/media-playback-pause.svgz</file> <file>16x16/media-playback-pause.svgz</file>
@@ -27,8 +35,14 @@
<file>16x16/media-seek-backward.svgz</file> <file>16x16/media-seek-backward.svgz</file>
<file>16x16/media-seek-forward.svgz</file> <file>16x16/media-seek-forward.svgz</file>
<file>16x16/network-connect.svgz</file> <file>16x16/network-connect.svgz</file>
<file>16x16/restore.svgz</file>
<file>16x16/style.svgz</file>
<file>16x16/system-shutdown.svgz</file>
<file>16x16/system-turnon.svgz</file>
<file>16x16/view-refresh.svgz</file> <file>16x16/view-refresh.svgz</file>
<file>16x16/view-refresh-purple.svgz</file>
<file>16x16/view-sort-ascending.svgz</file> <file>16x16/view-sort-ascending.svgz</file>
<file>16x16/wet.svgz</file>
<file>16x16/window-close.svgz</file> <file>16x16/window-close.svgz</file>
<file>16x16/zoom-fit-best.svgz</file> <file>16x16/zoom-fit-best.svgz</file>
<file>16x16/zoom-in.svgz</file> <file>16x16/zoom-in.svgz</file>


+ 101
- 149
resources/ui/carla_edit.ui View File

@@ -108,17 +108,17 @@
</spacer> </spacer>
</item> </item>
<item> <item>
<widget class="ScalableDial" name="dial_drywet">
<widget class="QWidget" name="dial_drywet">
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>34</width>
<height>34</height>
<width>32</width>
<height>48</height>
</size> </size>
</property> </property>
<property name="maximumSize"> <property name="maximumSize">
<size> <size>
<width>34</width>
<height>34</height>
<width>32</width>
<height>48</height>
</size> </size>
</property> </property>
<property name="contextMenuPolicy"> <property name="contextMenuPolicy">
@@ -130,17 +130,17 @@
</widget> </widget>
</item> </item>
<item> <item>
<widget class="ScalableDial" name="dial_vol">
<widget class="QWidget" name="dial_vol">
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>34</width>
<height>34</height>
<width>32</width>
<height>48</height>
</size> </size>
</property> </property>
<property name="maximumSize"> <property name="maximumSize">
<size> <size>
<width>34</width>
<height>34</height>
<width>32</width>
<height>48</height>
</size> </size>
</property> </property>
<property name="contextMenuPolicy"> <property name="contextMenuPolicy">
@@ -152,148 +152,92 @@
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QStackedWidget" name="stackedWidget">
<widget class="QWidget" name="dial_b_left">
<property name="minimumSize">
<size>
<width>26</width>
<height>40</height>
</size>
</property>
<property name="maximumSize"> <property name="maximumSize">
<size> <size>
<width>16777215</width>
<height>42</height>
<width>26</width>
<height>40</height>
</size> </size>
</property> </property>
<property name="lineWidth">
<number>0</number>
<property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum>
</property> </property>
<property name="currentIndex">
<number>0</number>
<property name="statusTip">
<string>Balance Left (0%)</string>
</property> </property>
<widget class="QWidget" name="page_bal">
<layout class="QHBoxLayout" name="horizontalLayout_6">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="ScalableDial" name="dial_b_left">
<property name="minimumSize">
<size>
<width>26</width>
<height>26</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>26</width>
<height>26</height>
</size>
</property>
<property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum>
</property>
<property name="statusTip">
<string>Balance Left (0%)</string>
</property>
</widget>
</item>
<item>
<widget class="ScalableDial" name="dial_b_right">
<property name="minimumSize">
<size>
<width>26</width>
<height>26</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>26</width>
<height>26</height>
</size>
</property>
<property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum>
</property>
<property name="statusTip">
<string>Balance Right (0%)</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="page_pan">
<layout class="QHBoxLayout" name="horizontalLayout_7">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="ScalableDial" name="dial_pan">
<property name="minimumSize">
<size>
<width>26</width>
<height>26</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>26</width>
<height>26</height>
</size>
</property>
<property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum>
</property>
<property name="statusTip">
<string>Balance Right (0%)</string>
</property>
</widget>
</item>
</layout>
</widget>
</widget> </widget>
</item> </item>
<item> <item>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="spacing">
<number>0</number>
<widget class="QWidget" name="dial_b_right">
<property name="minimumSize">
<size>
<width>26</width>
<height>40</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>26</width>
<height>40</height>
</size>
</property> </property>
<item>
<widget class="QRadioButton" name="rb_balance">
<property name="text">
<string>Use Balance</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="rb_pan">
<property name="text">
<string>Use Panning</string>
</property>
</widget>
</item>
</layout>
<property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum>
</property>
<property name="statusTip">
<string>Balance Right (0%)</string>
</property>
</widget>
</item>
<item>
<widget class="QWidget" name="dial_pan">
<property name="minimumSize">
<size>
<width>32</width>
<height>48</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>32</width>
<height>48</height>
</size>
</property>
<property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum>
</property>
<property name="statusTip">
<string>Left-Right (0%)</string>
</property>
</widget>
</item>
<item>
<widget class="QWidget" name="dial_forth">
<property name="minimumSize">
<size>
<width>32</width>
<height>48</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>32</width>
<height>48</height>
</size>
</property>
<property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum>
</property>
<property name="statusTip">
<string>Front-Rear (0%)</string>
</property>
</widget>
</item> </item>
<item> <item>
<spacer name="horizontalSpacer_4"> <spacer name="horizontalSpacer_4">
@@ -310,6 +254,21 @@
</item> </item>
</layout> </layout>
</item> </item>
<item>
<widget class="QLabel" name="label_ctrl_channel_2">
<property name="font">
<font>
<italic>true</italic>
</font>
</property>
<property name="text">
<string>&#9888; L, R are special mixing type.</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>
@@ -876,13 +835,6 @@ Plugin Name
</item> </item>
</layout> </layout>
</widget> </widget>
<customwidgets>
<customwidget>
<class>ScalableDial</class>
<extends>QDial</extends>
<header>widgets/scalabledial.h</header>
</customwidget>
</customwidgets>
<resources> <resources>
<include location="../resources.qrc"/> <include location="../resources.qrc"/>
</resources> </resources>


+ 99
- 1
resources/ui/carla_host.ui View File

@@ -474,6 +474,7 @@
<addaction name="act_file_refresh"/> <addaction name="act_file_refresh"/>
<addaction name="act_file_new"/> <addaction name="act_file_new"/>
<addaction name="act_file_open"/> <addaction name="act_file_open"/>
<addaction name="act_file_reload"/>
<addaction name="act_file_save"/> <addaction name="act_file_save"/>
<addaction name="act_file_save_as"/> <addaction name="act_file_save_as"/>
<addaction name="separator"/> <addaction name="separator"/>
@@ -508,6 +509,7 @@
<addaction name="separator"/> <addaction name="separator"/>
<addaction name="act_plugins_center"/> <addaction name="act_plugins_center"/>
<addaction name="separator"/> <addaction name="separator"/>
<addaction name="act_plugins_change_skin"/>
<addaction name="act_plugins_compact"/> <addaction name="act_plugins_compact"/>
<addaction name="act_plugins_expand"/> <addaction name="act_plugins_expand"/>
</widget> </widget>
@@ -548,6 +550,7 @@
<string>&amp;Settings</string> <string>&amp;Settings</string>
</property> </property>
<addaction name="act_settings_show_toolbar"/> <addaction name="act_settings_show_toolbar"/>
<addaction name="act_settings_show_toolbar_text"/>
<addaction name="act_settings_show_meters"/> <addaction name="act_settings_show_meters"/>
<addaction name="act_settings_show_keyboard"/> <addaction name="act_settings_show_keyboard"/>
<addaction name="act_settings_show_side_panel"/> <addaction name="act_settings_show_side_panel"/>
@@ -589,6 +592,7 @@
</attribute> </attribute>
<addaction name="act_file_new"/> <addaction name="act_file_new"/>
<addaction name="act_file_open"/> <addaction name="act_file_open"/>
<addaction name="act_file_reload"/>
<addaction name="act_file_save"/> <addaction name="act_file_save"/>
<addaction name="act_file_save_as"/> <addaction name="act_file_save_as"/>
<addaction name="act_file_connect"/> <addaction name="act_file_connect"/>
@@ -600,6 +604,17 @@
<addaction name="act_engine_panic"/> <addaction name="act_engine_panic"/>
<addaction name="separator"/> <addaction name="separator"/>
<addaction name="act_settings_configure"/> <addaction name="act_settings_configure"/>
<addaction name="separator"/>
<addaction name="act_plugins_enable"/>
<addaction name="act_plugins_disable"/>
<addaction name="act_plugins_bypass"/>
<addaction name="act_plugins_wet100"/>
<addaction name="act_plugins_mute"/>
<addaction name="act_plugins_volume100"/>
<addaction name="act_plugins_center"/>
<addaction name="act_plugins_change_skin"/>
<addaction name="act_plugins_compact"/>
<addaction name="act_plugins_expand"/>
</widget> </widget>
<widget class="QDockWidget" name="dockWidget"> <widget class="QDockWidget" name="dockWidget">
<property name="sizePolicy"> <property name="sizePolicy">
@@ -1111,6 +1126,30 @@
<enum>QAction::NoRole</enum> <enum>QAction::NoRole</enum>
</property> </property>
</action> </action>
<action name="act_file_reload">
<property name="icon">
<iconset resource="../resources.qrc">
<normaloff>:/16x16/view-refresh-purple.svgz</normaloff>:/16x16/view-refresh-purple.svgz</iconset>
</property>
<property name="text">
<string>&amp;Reload (!)</string>
</property>
<property name="iconText">
<string>Reload (!)</string>
</property>
<property name="toolTip">
<string>Reload file. CAUTION, non-saved changes will be LOST!</string>
</property>
<property name="shortcut">
<string>Ctrl+Shift+R</string>
</property>
<property name="visible">
<bool>false</bool>
</property>
<property name="menuRole">
<enum>QAction::NoRole</enum>
</property>
</action>
<action name="act_file_save"> <action name="act_file_save">
<property name="icon"> <property name="icon">
<iconset resource="../resources.qrc"> <iconset resource="../resources.qrc">
@@ -1220,6 +1259,10 @@
</property> </property>
</action> </action>
<action name="act_plugins_enable"> <action name="act_plugins_enable">
<property name="icon">
<iconset resource="../resources.qrc">
<normaloff>:/16x16/system-turnon.svgz</normaloff>:/16x16/system-turnon.svgz</iconset>
</property>
<property name="text"> <property name="text">
<string>Enable</string> <string>Enable</string>
</property> </property>
@@ -1228,6 +1271,10 @@
</property> </property>
</action> </action>
<action name="act_plugins_disable"> <action name="act_plugins_disable">
<property name="icon">
<iconset resource="../resources.qrc">
<normaloff>:/16x16/system-shutdown.svgz</normaloff>:/16x16/system-shutdown.svgz</iconset>
</property>
<property name="text"> <property name="text">
<string>Disable</string> <string>Disable</string>
</property> </property>
@@ -1236,6 +1283,10 @@
</property> </property>
</action> </action>
<action name="act_plugins_bypass"> <action name="act_plugins_bypass">
<property name="icon">
<iconset resource="../resources.qrc">
<normaloff>:/16x16/dry.svgz</normaloff>:/16x16/dry.svgz</iconset>
</property>
<property name="text"> <property name="text">
<string>0% Wet (Bypass)</string> <string>0% Wet (Bypass)</string>
</property> </property>
@@ -1244,6 +1295,10 @@
</property> </property>
</action> </action>
<action name="act_plugins_wet100"> <action name="act_plugins_wet100">
<property name="icon">
<iconset resource="../resources.qrc">
<normaloff>:/16x16/wet.svgz</normaloff>:/16x16/wet.svgz</iconset>
</property>
<property name="text"> <property name="text">
<string>100% Wet</string> <string>100% Wet</string>
</property> </property>
@@ -1252,6 +1307,10 @@
</property> </property>
</action> </action>
<action name="act_plugins_mute"> <action name="act_plugins_mute">
<property name="icon">
<iconset resource="../resources.qrc">
<normaloff>:/16x16/audio-volume-muted.svgz</normaloff>:/16x16/audio-volume-muted.svgz</iconset>
</property>
<property name="text"> <property name="text">
<string>0% Volume (Mute)</string> <string>0% Volume (Mute)</string>
</property> </property>
@@ -1260,6 +1319,10 @@
</property> </property>
</action> </action>
<action name="act_plugins_volume100"> <action name="act_plugins_volume100">
<property name="icon">
<iconset resource="../resources.qrc">
<normaloff>:/16x16/audio-volume-medium.svgz</normaloff>:/16x16/audio-volume-medium.svgz</iconset>
</property>
<property name="text"> <property name="text">
<string>100% Volume</string> <string>100% Volume</string>
</property> </property>
@@ -1268,6 +1331,10 @@
</property> </property>
</action> </action>
<action name="act_plugins_center"> <action name="act_plugins_center">
<property name="icon">
<iconset resource="../resources.qrc">
<normaloff>:/16x16/balance.svgz</normaloff>:/16x16/balance.svgz</iconset>
</property>
<property name="text"> <property name="text">
<string>Center Balance</string> <string>Center Balance</string>
</property> </property>
@@ -1435,6 +1502,17 @@
<enum>QAction::NoRole</enum> <enum>QAction::NoRole</enum>
</property> </property>
</action> </action>
<action name="act_settings_show_toolbar_text">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Show Toolbar Text</string>
</property>
<property name="menuRole">
<enum>QAction::NoRole</enum>
</property>
</action>
<action name="act_settings_configure"> <action name="act_settings_configure">
<property name="icon"> <property name="icon">
<iconset resource="../resources.qrc"> <iconset resource="../resources.qrc">
@@ -1553,7 +1631,23 @@
<enum>QAction::NoRole</enum> <enum>QAction::NoRole</enum>
</property> </property>
</action> </action>
<action name="act_plugins_change_skin">
<property name="icon">
<iconset resource="../resources.qrc">
<normaloff>:/16x16/style.svgz</normaloff>:/16x16/style.svgz</iconset>
</property>
<property name="text">
<string>Change &amp;Skin...</string>
</property>
<property name="menuRole">
<enum>QAction::NoRole</enum>
</property>
</action>
<action name="act_plugins_compact"> <action name="act_plugins_compact">
<property name="icon">
<iconset resource="../resources.qrc">
<normaloff>:/16x16/compact.svgz</normaloff>:/16x16/compact.svgz</iconset>
</property>
<property name="text"> <property name="text">
<string>Compact Slots</string> <string>Compact Slots</string>
</property> </property>
@@ -1562,6 +1656,10 @@
</property> </property>
</action> </action>
<action name="act_plugins_expand"> <action name="act_plugins_expand">
<property name="icon">
<iconset resource="../resources.qrc">
<normaloff>:/16x16/restore.svgz</normaloff>:/16x16/restore.svgz</iconset>
</property>
<property name="text"> <property name="text">
<string>Expand Slots</string> <string>Expand Slots</string>
</property> </property>
@@ -1597,7 +1695,7 @@
<action name="act_plugin_add_jack"> <action name="act_plugin_add_jack">
<property name="icon"> <property name="icon">
<iconset resource="../resources.qrc"> <iconset resource="../resources.qrc">
<normaloff>:/16x16/list-add.svgz</normaloff>:/16x16/list-add.svgz</iconset>
<normaloff>:/16x16/add-jack.svgz</normaloff>:/16x16/add-jack.svgz</iconset>
</property> </property>
<property name="text"> <property name="text">
<string>Add &amp;JACK Application...</string> <string>Add &amp;JACK Application...</string>


+ 21
- 0
resources/ui/carla_settings.ui View File

@@ -511,6 +511,27 @@
</property> </property>
</spacer> </spacer>
</item> </item>
<item row="2" column="0" colspan="3">
<layout class="QHBoxLayout" name="horizontalLayout_28">
<item>
<widget class="QLabel" name="label_main_skin_tweaks_1">
<property name="toolTip">
<string>Example: 'Tweak' is for all skins; extra 'skinnameTweak' overrides it for that skin only.</string>
</property>
<property name="text">
<string>Skin tweaks:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="le_main_skin_tweaks">
<property name="toolTip">
<string>Example: 'Tweak' is for all skins; extra 'skinnameTweak' overrides it for that skin only.</string>
</property>
</widget>
</item>
</layout>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>


+ 90
- 14
resources/ui/xycontroller.ui View File

@@ -30,41 +30,117 @@
<item> <item>
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QVBoxLayout" name="verticalLayout">
<item> <item>
<widget class="ScalableDial" name="dial_x">
<property name="minimum">
<number>-100</number>
<spacer name="verticalSpacer0">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="minimumSize">
<size>
<width>48</width>
<height>0</height>
</size>
</property> </property>
<property name="maximum">
<number>100</number>
</spacer>
</item>
<item>
<widget class="QWidget" name="dial_x">
<property name="minimumSize">
<size>
<width>48</width>
<height>58</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>48</width>
<height>58</height>
</size>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<spacer name="verticalSpacer">
<widget class="QWidget" name="dial_out_x">
<property name="minimumSize">
<size>
<width>48</width>
<height>58</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>48</width>
<height>58</height>
</size>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer1">
<property name="orientation"> <property name="orientation">
<enum>Qt::Vertical</enum> <enum>Qt::Vertical</enum>
</property> </property>
<property name="sizeType"> <property name="sizeType">
<enum>QSizePolicy::Fixed</enum> <enum>QSizePolicy::Fixed</enum>
</property> </property>
<property name="sizeHint" stdset="0">
<property name="minimumSize">
<size> <size>
<width>20</width>
<height>30</height>
<width>48</width>
<height>0</height>
</size> </size>
</property> </property>
</spacer> </spacer>
</item> </item>
<item> <item>
<widget class="ScalableDial" name="dial_y">
<property name="minimum">
<number>-100</number>
<widget class="QWidget" name="dial_y">
<property name="minimumSize">
<size>
<width>48</width>
<height>58</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>48</width>
<height>58</height>
</size>
</property> </property>
<property name="maximum">
<number>100</number>
</widget>
</item>
<item>
<widget class="QWidget" name="dial_out_y">
<property name="minimumSize">
<size>
<width>48</width>
<height>58</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>48</width>
<height>58</height>
</size>
</property> </property>
</widget> </widget>
</item> </item>
<item>
<spacer name="verticalSpacer2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="minimumSize">
<size>
<width>48</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
</layout> </layout>
</item> </item>
</layout> </layout>


+ 28
- 4
source/backend/plugin/CarlaPluginFluidSynth.cpp View File

@@ -947,7 +947,7 @@ public:
pData->hints |= PLUGIN_USES_MULTI_PROGS; pData->hints |= PLUGIN_USES_MULTI_PROGS;


if (! kUse16Outs) if (! kUse16Outs)
pData->hints |= PLUGIN_CAN_BALANCE;
pData->hints |= PLUGIN_CAN_BALANCE | PLUGIN_CAN_PANNING;


// extra plugin hints // extra plugin hints
pData->extraHints = 0x0; pData->extraHints = 0x0;
@@ -1521,7 +1521,7 @@ public:


{ {
// note - balance not possible with kUse16Outs, so we can safely skip fAudioOutBuffers // note - balance not possible with kUse16Outs, so we can safely skip fAudioOutBuffers
const bool doVolume = (pData->hints & PLUGIN_CAN_VOLUME) != 0 && carla_isNotEqual(pData->postProc.volume, 1.0f);
const bool doVolume = (pData->hints & PLUGIN_CAN_VOLUME) != 0 && (carla_isNotEqual(pData->postProc.volume, 1.0f) || carla_isNotEqual(pData->postProc.panning, 0.0f));
const bool doBalance = (pData->hints & PLUGIN_CAN_BALANCE) != 0 && ! (carla_isEqual(pData->postProc.balanceLeft, -1.0f) && carla_isEqual(pData->postProc.balanceRight, 1.0f)); const bool doBalance = (pData->hints & PLUGIN_CAN_BALANCE) != 0 && ! (carla_isEqual(pData->postProc.balanceLeft, -1.0f) && carla_isEqual(pData->postProc.balanceRight, 1.0f));


float* const oldBufLeft = pData->postProc.extraBuffer; float* const oldBufLeft = pData->postProc.extraBuffer;
@@ -1554,16 +1554,40 @@ public:
} }
} }


// Panning
// Only decrease of levels, but never increase, unlike 'L, R'.
// Note: no any pan processing for Mono.

uint32_t q = pData->audioOut.count;
float pan = pData->postProc.panning;
float vol = pData->postProc.volume;

// Pan: Stereo only.
if ((pan != 0.0) && (q == 2))
{
// left channel(s) reduce when pan to right
if ((pan > 0) && (i == 0))
{
vol = vol * (1.0 - pan);
}

// right channel(s) reduce when pan to left
else if ((pan < 0) && (i == 1))
{
vol = vol * (1.0 + pan);
}
}

// Volume // Volume
if (kUse16Outs) if (kUse16Outs)
{ {
for (uint32_t k=0; k < frames; ++k) for (uint32_t k=0; k < frames; ++k)
outBuffer[i][k+timeOffset] = fAudio16Buffers[i][k] * pData->postProc.volume;
outBuffer[i][k+timeOffset] = fAudio16Buffers[i][k] * vol;
} }
else if (doVolume) else if (doVolume)
{ {
for (uint32_t k=0; k < frames; ++k) for (uint32_t k=0; k < frames; ++k)
outBuffer[i][k+timeOffset] *= pData->postProc.volume;
outBuffer[i][k+timeOffset] *= vol;
} }
} }




+ 32
- 1
source/backend/plugin/CarlaPluginLADSPADSSI.cpp View File

@@ -1271,6 +1271,9 @@ public:


if (aOuts >= 2 && aOuts % 2 == 0) if (aOuts >= 2 && aOuts % 2 == 0)
pData->hints |= PLUGIN_CAN_BALANCE; pData->hints |= PLUGIN_CAN_BALANCE;

if (aOuts >= 2)
pData->hints |= PLUGIN_CAN_PANNING;
#endif #endif


// extra plugin hints // extra plugin hints
@@ -2083,7 +2086,11 @@ public:
fAudioOutBuffers[i][k] = (fAudioOutBuffers[i][k] * pData->postProc.dryWet) + (bufValue * (1.0f - pData->postProc.dryWet)); fAudioOutBuffers[i][k] = (fAudioOutBuffers[i][k] * pData->postProc.dryWet) + (bufValue * (1.0f - pData->postProc.dryWet));
} }
} }
}


// Do not join this loop with loop above.
for (uint32_t i=0; i < pData->audioOut.count; ++i)
{
// Balance // Balance
if (doBalance) if (doBalance)
{ {
@@ -2115,10 +2122,34 @@ public:
} }
} }


// Panning
// Only decrease of levels, but never increase, unlike 'L, R'.
// Note: no pan processing for Mono.

uint32_t q = pData->audioOut.count;
float pan = pData->postProc.panning;
float vol = pData->postProc.volume;

// Pan: Stereo, 3 ch (extra rear/bass), or Quadro.
if ((pan != 0.0) && ((q == 2) || (q == 3) || (q == 4)))
{
// left channel(s) reduce when pan to right
if ((pan > 0) && ((i == 0) || ((i == 2) && (q == 4))))
{
vol = vol * (1.0 - pan);
}

// right channel(s) reduce when pan to left
else if ((pan < 0) && ((i == 1) || (i == 3)))
{
vol = vol * (1.0 + pan);
}
}

// Volume (and buffer copy) // Volume (and buffer copy)
{ {
for (uint32_t k=0; k < frames; ++k) for (uint32_t k=0; k < frames; ++k)
audioOut[i][k+timeOffset] = fAudioOutBuffers[i][k] * pData->postProc.volume;
audioOut[i][k+timeOffset] = fAudioOutBuffers[i][k] * vol;
} }
} }




+ 32
- 1
source/backend/plugin/CarlaPluginLV2.cpp View File

@@ -3349,6 +3349,9 @@ public:
if (aOuts >= 2 && aOuts % 2 == 0) if (aOuts >= 2 && aOuts % 2 == 0)
pData->hints |= PLUGIN_CAN_BALANCE; pData->hints |= PLUGIN_CAN_BALANCE;


if (aOuts >= 2)
pData->hints |= PLUGIN_CAN_PANNING;

// extra plugin hints // extra plugin hints
pData->extraHints = 0x0; pData->extraHints = 0x0;


@@ -4684,7 +4687,11 @@ public:
fAudioOutBuffers[i][k] = (fAudioOutBuffers[i][k] * pData->postProc.dryWet) + (bufValue * (1.0f - pData->postProc.dryWet)); fAudioOutBuffers[i][k] = (fAudioOutBuffers[i][k] * pData->postProc.dryWet) + (bufValue * (1.0f - pData->postProc.dryWet));
} }
} }
}


// Do not join this loop with loop above.
for (uint32_t i=0; i < pData->audioOut.count; ++i)
{
// Balance // Balance
if (doBalance) if (doBalance)
{ {
@@ -4716,10 +4723,34 @@ public:
} }
} }


// Panning
// Only decrease of levels, but never increase, unlike 'L, R'.
// Note: no pan processing for Mono.

uint32_t q = pData->audioOut.count;
float pan = pData->postProc.panning;
float vol = pData->postProc.volume;

// Pan: Stereo, 3 ch (extra rear/bass), or Quadro.
if ((pan != 0.0) && ((q == 2) || (q == 3) || (q == 4)))
{
// left channel(s) reduce when pan to right
if ((pan > 0) && ((i == 0) || ((i == 2) && (q == 4))))
{
vol = vol * (1.0 - pan);
}

// right channel(s) reduce when pan to left
else if ((pan < 0) && ((i == 1) || (i == 3)))
{
vol = vol * (1.0 + pan);
}
}

// Volume (and buffer copy) // Volume (and buffer copy)
{ {
for (uint32_t k=0; k < frames; ++k) for (uint32_t k=0; k < frames; ++k)
audioOut[i][k+timeOffset] = fAudioOutBuffers[i][k] * pData->postProc.volume;
audioOut[i][k+timeOffset] = fAudioOutBuffers[i][k] * vol;
} }
} }
} // End of Post-processing } // End of Post-processing


+ 33
- 2
source/backend/plugin/CarlaPluginNative.cpp View File

@@ -1405,6 +1405,9 @@ public:
if (aOuts >= 2 && aOuts % 2 == 0) if (aOuts >= 2 && aOuts % 2 == 0)
pData->hints |= PLUGIN_CAN_BALANCE; pData->hints |= PLUGIN_CAN_BALANCE;


if (aOuts >= 2)
pData->hints |= PLUGIN_CAN_PANNING;

// native plugin hints // native plugin hints
if (fDescriptor->hints & NATIVE_PLUGIN_IS_RTSAFE) if (fDescriptor->hints & NATIVE_PLUGIN_IS_RTSAFE)
pData->hints |= PLUGIN_IS_RTSAFE; pData->hints |= PLUGIN_IS_RTSAFE;
@@ -2369,7 +2372,7 @@ public:
float bufValue; float bufValue;
float* const oldBufLeft = pData->postProc.extraBuffer; float* const oldBufLeft = pData->postProc.extraBuffer;


for (; i < pData->audioOut.count; ++i)
for (uint32_t i=0; i < pData->audioOut.count; ++i)
{ {
// Dry/Wet // Dry/Wet
if (doDryWet) if (doDryWet)
@@ -2380,7 +2383,11 @@ public:
fAudioAndCvOutBuffers[i][k] = (fAudioAndCvOutBuffers[i][k] * pData->postProc.dryWet) + (bufValue * (1.0f - pData->postProc.dryWet)); fAudioAndCvOutBuffers[i][k] = (fAudioAndCvOutBuffers[i][k] * pData->postProc.dryWet) + (bufValue * (1.0f - pData->postProc.dryWet));
} }
} }
}


// Do not join this loop with loop above.
for (uint32_t i=0; i < pData->audioOut.count; ++i)
{
// Balance // Balance
if (doBalance) if (doBalance)
{ {
@@ -2412,10 +2419,34 @@ public:
} }
} }


// Panning
// Only decrease of levels, but never increase, unlike 'L, R'.
// Note: no pan processing for Mono.

uint32_t q = pData->audioOut.count;
float pan = pData->postProc.panning;
float vol = pData->postProc.volume;

// Pan: Stereo, 3 ch (extra rear/bass), or Quadro.
if ((pan != 0.0) && ((q == 2) || (q == 3) || (q == 4)))
{
// left channel(s) reduce when pan to right
if ((pan > 0) && ((i == 0) || ((i == 2) && (q == 4))))
{
vol = vol * (1.0 - pan);
}

// right channel(s) reduce when pan to left
else if ((pan < 0) && ((i == 1) || (i == 3)))
{
vol = vol * (1.0 + pan);
}
}

// Volume (and buffer copy) // Volume (and buffer copy)
{ {
for (uint32_t k=0; k < frames; ++k) for (uint32_t k=0; k < frames; ++k)
audioOut[i][k+timeOffset] = fAudioAndCvOutBuffers[i][k] * pData->postProc.volume;
audioOut[i][k+timeOffset] = fAudioAndCvOutBuffers[i][k] * vol;
} }
} }




+ 10
- 0
source/frontend/carla_backend.py View File

@@ -310,6 +310,16 @@ PARAMETER_CAN_BE_CV_CONTROLLED = 0x800
# @note only valid for parameter inputs. # @note only valid for parameter inputs.
PARAMETER_IS_NOT_SAVED = 0x1000 PARAMETER_IS_NOT_SAVED = 0x1000


# Human readable labels for 24 decoded bits (currently for XRay tab of Edit dialog).
# Are some hints can exceed 2^24 ?
parameterHintsText = (
"IS_BOOLEAN", "IS_INTEGER", "IS_LOGARITHMIC", "n/a",
"IS_ENABLED", "IS_AUTOMATABLE", "IS_READ_ONLY", "n/a",
"USES_SAMPLERATE", "USES_SCALEPOINTS", "USES_CUSTOM_TEXT", "CAN_BE_CV_CONTROLLED",
"IS_NOT_SAVED", "n/a", "n/a", "n/a",
"n/a", "n/a", "n/a", "n/a",
"n/a", "n/a", "n/a", "n/a", )

# --------------------------------------------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------------------------------------------
# Mapped Parameter Flags # Mapped Parameter Flags
# Various flags for parameter mappings. # Various flags for parameter mappings.


+ 146
- 13
source/frontend/carla_host.py View File

@@ -54,6 +54,7 @@ if qt_config == 5:
QListWidgetItem, QListWidgetItem,
QGraphicsView, QGraphicsView,
QMainWindow, QMainWindow,
QToolButton,
) )


elif qt_config == 6: elif qt_config == 6:
@@ -86,6 +87,7 @@ elif qt_config == 6:
QListWidgetItem, QListWidgetItem,
QGraphicsView, QGraphicsView,
QMainWindow, QMainWindow,
QToolButton,
) )


# ------------------------------------------------------------------------------------------------------------ # ------------------------------------------------------------------------------------------------------------
@@ -101,6 +103,7 @@ from carla_shared import *
from carla_settings import * from carla_settings import *
from carla_utils import * from carla_utils import *
from carla_widgets import * from carla_widgets import *
from carla_skin import *


from patchcanvas import patchcanvas from patchcanvas import patchcanvas
from widgets.digitalpeakmeter import DigitalPeakMeter from widgets.digitalpeakmeter import DigitalPeakMeter
@@ -221,6 +224,8 @@ class HostWindow(QMainWindow):
self.fOscAddressTCP = "" self.fOscAddressTCP = ""
self.fOscAddressUDP = "" self.fOscAddressUDP = ""


self.slowTimer = 0

if CARLA_OS_MAC: if CARLA_OS_MAC:
self.fMacClosingHelper = True self.fMacClosingHelper = True


@@ -270,6 +275,8 @@ class HostWindow(QMainWindow):


self.fWithCanvas = withCanvas self.fWithCanvas = withCanvas


self.fTweaks = {}

# ---------------------------------------------------------------------------------------------------- # ----------------------------------------------------------------------------------------------------
# Internal stuff (logs) # Internal stuff (logs)


@@ -293,6 +300,7 @@ class HostWindow(QMainWindow):
if self.host.isControl: if self.host.isControl:
self.ui.act_file_new.setVisible(False) self.ui.act_file_new.setVisible(False)
self.ui.act_file_open.setVisible(False) self.ui.act_file_open.setVisible(False)
self.ui.act_file_reload.setVisible(False)
self.ui.act_file_save_as.setVisible(False) self.ui.act_file_save_as.setVisible(False)
self.ui.tabUtils.removeTab(0) self.ui.tabUtils.removeTab(0)
else: else:
@@ -319,10 +327,12 @@ class HostWindow(QMainWindow):
self.ui.act_file_new.setEnabled(False) self.ui.act_file_new.setEnabled(False)


self.ui.act_file_open.setEnabled(False) self.ui.act_file_open.setEnabled(False)
self.ui.act_file_reload.setEnabled(False)
self.ui.act_file_save.setEnabled(False) self.ui.act_file_save.setEnabled(False)
self.ui.act_file_save_as.setEnabled(False) self.ui.act_file_save_as.setEnabled(False)
self.ui.act_engine_stop.setEnabled(False) self.ui.act_engine_stop.setEnabled(False)
self.ui.act_plugin_remove_all.setEnabled(False)
# self.ui.act_plugin_remove_all.setEnabled(False)
self.setMenuMacrosEnabled(False)


self.ui.act_canvas_show_internal.setChecked(False) self.ui.act_canvas_show_internal.setChecked(False)
self.ui.act_canvas_show_internal.setVisible(False) self.ui.act_canvas_show_internal.setVisible(False)
@@ -503,6 +513,7 @@ class HostWindow(QMainWindow):
self.ui.act_file_refresh.setIcon(getIcon('view-refresh', 16, 'svgz')) self.ui.act_file_refresh.setIcon(getIcon('view-refresh', 16, 'svgz'))
self.ui.act_file_new.setIcon(getIcon('document-new', 16, 'svgz')) self.ui.act_file_new.setIcon(getIcon('document-new', 16, 'svgz'))
self.ui.act_file_open.setIcon(getIcon('document-open', 16, 'svgz')) self.ui.act_file_open.setIcon(getIcon('document-open', 16, 'svgz'))
self.ui.act_file_reload.setIcon(getIcon('view-refresh-purple', 16, 'svgz'))
self.ui.act_file_save.setIcon(getIcon('document-save', 16, 'svgz')) self.ui.act_file_save.setIcon(getIcon('document-save', 16, 'svgz'))
self.ui.act_file_save_as.setIcon(getIcon('document-save-as', 16, 'svgz')) self.ui.act_file_save_as.setIcon(getIcon('document-save-as', 16, 'svgz'))
self.ui.act_file_quit.setIcon(getIcon('application-exit', 16, 'svgz')) self.ui.act_file_quit.setIcon(getIcon('application-exit', 16, 'svgz'))
@@ -511,8 +522,18 @@ class HostWindow(QMainWindow):
self.ui.act_engine_panic.setIcon(getIcon('dialog-warning', 16, 'svgz')) self.ui.act_engine_panic.setIcon(getIcon('dialog-warning', 16, 'svgz'))
self.ui.act_engine_config.setIcon(getIcon('configure', 16, 'svgz')) self.ui.act_engine_config.setIcon(getIcon('configure', 16, 'svgz'))
self.ui.act_plugin_add.setIcon(getIcon('list-add', 16, 'svgz')) self.ui.act_plugin_add.setIcon(getIcon('list-add', 16, 'svgz'))
self.ui.act_plugin_add_jack.setIcon(getIcon('list-add', 16, 'svgz'))
self.ui.act_plugin_add_jack.setIcon(getIcon('add-jack', 16, 'svgz'))
self.ui.act_plugin_remove_all.setIcon(getIcon('edit-delete', 16, 'svgz')) self.ui.act_plugin_remove_all.setIcon(getIcon('edit-delete', 16, 'svgz'))
self.ui.act_plugins_enable.setIcon(getIcon('system-turnon', 16, 'svgz'))
self.ui.act_plugins_disable.setIcon(getIcon('system-shutdown', 16, 'svgz'))
self.ui.act_plugins_bypass.setIcon(getIcon('dry', 16, 'svgz'))
self.ui.act_plugins_wet100.setIcon(getIcon('wet', 16, 'svgz'))
self.ui.act_plugins_mute.setIcon(getIcon('audio-volume-muted', 16, 'svgz'))
self.ui.act_plugins_volume100.setIcon(getIcon('audio-volume-medium', 16, 'svgz'))
self.ui.act_plugins_center.setIcon(getIcon('balance', 16, 'svgz'))
self.ui.act_plugins_change_skin.setIcon(getIcon('skin', 16, 'svgz'))
self.ui.act_plugins_compact.setIcon(getIcon('compact', 16, 'svgz'))
self.ui.act_plugins_expand.setIcon(getIcon('restore', 16, 'svgz'))
self.ui.act_canvas_arrange.setIcon(getIcon('view-sort-ascending', 16, 'svgz')) self.ui.act_canvas_arrange.setIcon(getIcon('view-sort-ascending', 16, 'svgz'))
self.ui.act_canvas_refresh.setIcon(getIcon('view-refresh', 16, 'svgz')) self.ui.act_canvas_refresh.setIcon(getIcon('view-refresh', 16, 'svgz'))
self.ui.act_canvas_zoom_fit.setIcon(getIcon('zoom-fit-best', 16, 'svgz')) self.ui.act_canvas_zoom_fit.setIcon(getIcon('zoom-fit-best', 16, 'svgz'))
@@ -534,6 +555,7 @@ class HostWindow(QMainWindow):


self.ui.act_file_new.triggered.connect(self.slot_fileNew) self.ui.act_file_new.triggered.connect(self.slot_fileNew)
self.ui.act_file_open.triggered.connect(self.slot_fileOpen) self.ui.act_file_open.triggered.connect(self.slot_fileOpen)
self.ui.act_file_reload.triggered.connect(self.slot_fileReload)
self.ui.act_file_save.triggered.connect(self.slot_fileSave) self.ui.act_file_save.triggered.connect(self.slot_fileSave)
self.ui.act_file_save_as.triggered.connect(self.slot_fileSaveAs) self.ui.act_file_save_as.triggered.connect(self.slot_fileSaveAs)


@@ -553,10 +575,12 @@ class HostWindow(QMainWindow):
self.ui.act_plugins_wet100.triggered.connect(self.slot_pluginsWet100) self.ui.act_plugins_wet100.triggered.connect(self.slot_pluginsWet100)
self.ui.act_plugins_bypass.triggered.connect(self.slot_pluginsBypass) self.ui.act_plugins_bypass.triggered.connect(self.slot_pluginsBypass)
self.ui.act_plugins_center.triggered.connect(self.slot_pluginsCenter) self.ui.act_plugins_center.triggered.connect(self.slot_pluginsCenter)
self.ui.act_plugins_change_skin.triggered.connect(self.slot_pluginsChangeSkin)
self.ui.act_plugins_compact.triggered.connect(self.slot_pluginsCompact) self.ui.act_plugins_compact.triggered.connect(self.slot_pluginsCompact)
self.ui.act_plugins_expand.triggered.connect(self.slot_pluginsExpand) self.ui.act_plugins_expand.triggered.connect(self.slot_pluginsExpand)


self.ui.act_settings_show_toolbar.toggled.connect(self.slot_showToolbar) self.ui.act_settings_show_toolbar.toggled.connect(self.slot_showToolbar)
self.ui.act_settings_show_toolbar_text.toggled.connect(self.slot_showToolbarText)
self.ui.act_settings_show_meters.toggled.connect(self.slot_showCanvasMeters) self.ui.act_settings_show_meters.toggled.connect(self.slot_showCanvasMeters)
self.ui.act_settings_show_keyboard.toggled.connect(self.slot_showCanvasKeyboard) self.ui.act_settings_show_keyboard.toggled.connect(self.slot_showCanvasKeyboard)
self.ui.act_settings_show_side_panel.toggled.connect(self.slot_showSidePanel) self.ui.act_settings_show_side_panel.toggled.connect(self.slot_showSidePanel)
@@ -738,7 +762,7 @@ class HostWindow(QMainWindow):
self.host.set_custom_data(pluginId, CUSTOM_DATA_TYPE_PROPERTY, "CarlaColor", colorStr) self.host.set_custom_data(pluginId, CUSTOM_DATA_TYPE_PROPERTY, "CarlaColor", colorStr)
pitem.recreateWidget(newColor = color) pitem.recreateWidget(newColor = color)


def changePluginSkin(self, pluginId, skin):
def changePluginSkin(self, pluginId, skin, color = None):
if pluginId > self.fPluginCount: if pluginId > self.fPluginCount:
return return


@@ -748,7 +772,9 @@ class HostWindow(QMainWindow):
return return


self.host.set_custom_data(pluginId, CUSTOM_DATA_TYPE_PROPERTY, "CarlaSkin", skin) self.host.set_custom_data(pluginId, CUSTOM_DATA_TYPE_PROPERTY, "CarlaSkin", skin)
if skin not in ("default","rncbc","presets","mpresets"):
if color is not None:
pitem.recreateWidget(newSkin = skin, newColor = color)
elif skin not in ("default","rncbc","presets","mpresets"):
pitem.recreateWidget(newSkin = skin, newColor = (255,255,255)) pitem.recreateWidget(newSkin = skin, newColor = (255,255,255))
else: else:
pitem.recreateWidget(newSkin = skin) pitem.recreateWidget(newSkin = skin)
@@ -935,6 +961,16 @@ class HostWindow(QMainWindow):
self.loadProjectNow() self.loadProjectNow()
self.fProjectFilename = filenameOld self.fProjectFilename = filenameOld


if self.fTweaks.get('ShowReload', 0):
self.ui.act_file_reload.setEnabled(True)
self.ui.act_file_reload.setVisible(True)

@pyqtSlot()
def slot_fileReload(self):
if not (self.fProjectFilename == ""):
self.pluginRemoveAll()
self.loadProjectNow()

@pyqtSlot() @pyqtSlot()
def slot_fileSave(self, saveAs=False): def slot_fileSave(self, saveAs=False):
if self.fProjectFilename and not saveAs: if self.fProjectFilename and not saveAs:
@@ -1177,6 +1213,7 @@ class HostWindow(QMainWindow):


if self.host.isPlugin or not self.fSessionManagerName: if self.host.isPlugin or not self.fSessionManagerName:
self.ui.act_file_open.setEnabled(False) self.ui.act_file_open.setEnabled(False)
self.ui.act_file_reload.setEnabled(False)
self.ui.act_file_save_as.setEnabled(False) self.ui.act_file_save_as.setEnabled(False)


@pyqtSlot(int, str) @pyqtSlot(int, str)
@@ -1226,7 +1263,8 @@ class HostWindow(QMainWindow):
# Plugins # Plugins


def removeAllPlugins(self): def removeAllPlugins(self):
self.ui.act_plugin_remove_all.setEnabled(False)
# self.ui.act_plugin_remove_all.setEnabled(False)
self.setMenuMacrosEnabled(False)
patchcanvas.handleAllPluginsRemoved() patchcanvas.handleAllPluginsRemoved()


while self.ui.listWidget.takeItem(0): while self.ui.listWidget.takeItem(0):
@@ -1343,6 +1381,12 @@ class HostWindow(QMainWindow):
act = fmenu.addAction(p['name']) act = fmenu.addAction(p['name'])
act.setData(p) act.setData(p)
act.triggered.connect(self.slot_favoritePluginAdd) act.triggered.connect(self.slot_favoritePluginAdd)

if self.fSavedSettings[CARLA_KEY_MAIN_SYSTEM_ICONS]:
fmenu.setIcon(getIcon('add-from-favorites', 16, 'svgz'))
else:
fmenu.setIcon(QIcon(":/16x16/add-from-favorites.svgz"))

menu.addMenu(fmenu) menu.addMenu(fmenu)


menu.addAction(self.ui.act_plugin_remove_all) menu.addAction(self.ui.act_plugin_remove_all)
@@ -1359,6 +1403,7 @@ class HostWindow(QMainWindow):
menu.addSeparator() menu.addSeparator()
menu.addAction(self.ui.act_plugins_center) menu.addAction(self.ui.act_plugins_center)
menu.addSeparator() menu.addSeparator()
menu.addAction(self.ui.act_plugins_change_skin)
menu.addAction(self.ui.act_plugins_compact) menu.addAction(self.ui.act_plugins_compact)
menu.addAction(self.ui.act_plugins_expand) menu.addAction(self.ui.act_plugins_expand)


@@ -1454,7 +1499,7 @@ class HostWindow(QMainWindow):
if pitem is None: if pitem is None:
break break


pitem.getWidget().setInternalParameter(PLUGIN_CAN_VOLUME, 1.0)
pitem.getWidget().setInternalParameter(PARAMETER_VOLUME, 1.0)


@pyqtSlot() @pyqtSlot()
def slot_pluginsMute(self): def slot_pluginsMute(self):
@@ -1465,7 +1510,7 @@ class HostWindow(QMainWindow):
if pitem is None: if pitem is None:
break break


pitem.getWidget().setInternalParameter(PLUGIN_CAN_VOLUME, 0.0)
pitem.getWidget().setInternalParameter(PARAMETER_VOLUME, 0.0)


@pyqtSlot() @pyqtSlot()
def slot_pluginsWet100(self): def slot_pluginsWet100(self):
@@ -1476,7 +1521,7 @@ class HostWindow(QMainWindow):
if pitem is None: if pitem is None:
break break


pitem.getWidget().setInternalParameter(PLUGIN_CAN_DRYWET, 1.0)
pitem.getWidget().setInternalParameter(PARAMETER_DRYWET, 1.0)


@pyqtSlot() @pyqtSlot()
def slot_pluginsBypass(self): def slot_pluginsBypass(self):
@@ -1487,7 +1532,7 @@ class HostWindow(QMainWindow):
if pitem is None: if pitem is None:
break break


pitem.getWidget().setInternalParameter(PLUGIN_CAN_DRYWET, 0.0)
pitem.getWidget().setInternalParameter(PARAMETER_DRYWET, 0.0)


@pyqtSlot() @pyqtSlot()
def slot_pluginsCenter(self): def slot_pluginsCenter(self):
@@ -1502,6 +1547,38 @@ class HostWindow(QMainWindow):
pitem.getWidget().setInternalParameter(PARAMETER_BALANCE_RIGHT, 1.0) pitem.getWidget().setInternalParameter(PARAMETER_BALANCE_RIGHT, 1.0)
pitem.getWidget().setInternalParameter(PARAMETER_PANNING, 0.0) pitem.getWidget().setInternalParameter(PARAMETER_PANNING, 0.0)


@pyqtSlot()
def slot_pluginsChangeSkin(self):
skin = QInputDialog.getItem(self, self.tr("Change Skin"), self.tr("Change Skin to:"), skinList, 0, False)
if not all(skin):
return

if skin[0][:4] in ("calf", "clas", "zynf"): # These are non-colorable
for pluginId in range(self.fPluginCount):
gCarla.gui.changePluginSkin(pluginId, skin[0])
return

reColor = QInputDialog.getItem(self, self.tr("Change Color"), self.tr("Change Color mode:"), ['Follow Rules / As Is', 'Random'], 0, False)
if not all(reColor):
return

color = None
if skin[0].startswith("3ba"): # These are dark tinted, need enlight.
color = (254,255,255) # If not random
luma = 0.5
sat = 0.5
elif skin[0].startswith("ope"): # These are dark tinted, need enlight.
luma = 0.5
sat = 1.0
else:
luma = 0.125
sat = 0.25

for pluginId in range(self.fPluginCount):
if reColor[0][:2] == 'Ra':
color = QColor.fromHslF(random.random(), sat, luma, 1).getRgb()[0:3]
gCarla.gui.changePluginSkin(pluginId, skin[0], color)

@pyqtSlot() @pyqtSlot()
def slot_pluginsCompact(self): def slot_pluginsCompact(self):
for pitem in self.fPluginList: for pitem in self.fPluginList:
@@ -1531,11 +1608,24 @@ class HostWindow(QMainWindow):
self.fPluginList.append(pitem) self.fPluginList.append(pitem)
self.fPluginCount += 1 self.fPluginCount += 1


self.ui.act_plugin_remove_all.setEnabled(self.fPluginCount > 0)
self.setMenuMacrosEnabled(self.fPluginCount > 0)


if pluginType == PLUGIN_LV2: if pluginType == PLUGIN_LV2:
self.fHasLoadedLv2Plugins = True self.fHasLoadedLv2Plugins = True


def setMenuMacrosEnabled(self, enabled):
self.ui.act_plugin_remove_all.setEnabled(enabled)
self.ui.act_plugins_enable.setEnabled(enabled)
self.ui.act_plugins_disable.setEnabled(enabled)
self.ui.act_plugins_volume100.setEnabled(enabled)
self.ui.act_plugins_mute.setEnabled(enabled)
self.ui.act_plugins_wet100.setEnabled(enabled)
self.ui.act_plugins_bypass.setEnabled(enabled)
self.ui.act_plugins_center.setEnabled(enabled)
self.ui.act_plugins_change_skin.setEnabled(enabled)
self.ui.act_plugins_compact.setEnabled(enabled)
self.ui.act_plugins_expand.setEnabled(enabled)

@pyqtSlot(int) @pyqtSlot(int)
def slot_handlePluginRemovedCallback(self, pluginId): def slot_handlePluginRemovedCallback(self, pluginId):
if self.fWithCanvas: if self.fWithCanvas:
@@ -1558,7 +1648,8 @@ class HostWindow(QMainWindow):
del pitem del pitem


if self.fPluginCount == 0: if self.fPluginCount == 0:
self.ui.act_plugin_remove_all.setEnabled(False)
# self.ui.act_plugin_remove_all.setEnabled(False)
self.setMenuMacrosEnabled(False)
if self.fCurrentlyRemovingAllPlugins: if self.fCurrentlyRemovingAllPlugins:
self.fCurrentlyRemovingAllPlugins = False self.fCurrentlyRemovingAllPlugins = False
self.projectLoadingFinished(False) self.projectLoadingFinished(False)
@@ -1569,7 +1660,9 @@ class HostWindow(QMainWindow):
pitem = self.fPluginList[i] pitem = self.fPluginList[i]
pitem.setPluginId(i) pitem.setPluginId(i)


self.ui.act_plugin_remove_all.setEnabled(True)
# self.ui.act_plugin_remove_all.setEnabled(True)
self.setMenuMacrosEnabled(True)



# -------------------------------------------------------------------------------------------------------- # --------------------------------------------------------------------------------------------------------
# Canvas # Canvas
@@ -1975,6 +2068,7 @@ class HostWindow(QMainWindow):


settings.setValue("Geometry", self.saveGeometry()) settings.setValue("Geometry", self.saveGeometry())
settings.setValue("ShowToolbar", self.ui.toolBar.isEnabled()) settings.setValue("ShowToolbar", self.ui.toolBar.isEnabled())
settings.setValue("ShowToolbarText", self.ui.act_settings_show_toolbar_text.isChecked())
settings.setValue("ShowSidePanel", self.ui.dockWidget.isEnabled()) settings.setValue("ShowSidePanel", self.ui.dockWidget.isEnabled())


diskFolders = [] diskFolders = []
@@ -2009,11 +2103,24 @@ class HostWindow(QMainWindow):


showToolbar = settings.value("ShowToolbar", True, bool) showToolbar = settings.value("ShowToolbar", True, bool)
self.ui.act_settings_show_toolbar.setChecked(showToolbar) self.ui.act_settings_show_toolbar.setChecked(showToolbar)
self.ui.act_settings_show_toolbar_text.setEnabled(showToolbar)
self.ui.toolBar.blockSignals(True) self.ui.toolBar.blockSignals(True)
self.ui.toolBar.setEnabled(showToolbar) self.ui.toolBar.setEnabled(showToolbar)
self.ui.toolBar.setVisible(showToolbar) self.ui.toolBar.setVisible(showToolbar)
self.ui.toolBar.blockSignals(False) self.ui.toolBar.blockSignals(False)


showToolbarText = settings.value("ShowToolbarText", True, bool)
self.ui.act_settings_show_toolbar_text.setChecked(showToolbarText)
self.ui.toolBar.blockSignals(True)

for btn in self.ui.toolBar.findChildren(QToolButton):
if showToolbarText:
btn.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
else:
btn.setToolButtonStyle(Qt.ToolButtonIconOnly)

self.ui.toolBar.blockSignals(False)

#if settings.contains("SplitterState"): #if settings.contains("SplitterState"):
#self.ui.splitter.restoreState(settings.value("SplitterState", b"")) #self.ui.splitter.restoreState(settings.value("SplitterState", b""))
#else: #else:
@@ -2066,6 +2173,7 @@ class HostWindow(QMainWindow):
CARLA_KEY_MAIN_REFRESH_INTERVAL: settings.value(CARLA_KEY_MAIN_REFRESH_INTERVAL, CARLA_DEFAULT_MAIN_REFRESH_INTERVAL, int), CARLA_KEY_MAIN_REFRESH_INTERVAL: settings.value(CARLA_KEY_MAIN_REFRESH_INTERVAL, CARLA_DEFAULT_MAIN_REFRESH_INTERVAL, int),
CARLA_KEY_MAIN_SYSTEM_ICONS: settings.value(CARLA_KEY_MAIN_SYSTEM_ICONS, CARLA_DEFAULT_MAIN_SYSTEM_ICONS, bool), CARLA_KEY_MAIN_SYSTEM_ICONS: settings.value(CARLA_KEY_MAIN_SYSTEM_ICONS, CARLA_DEFAULT_MAIN_SYSTEM_ICONS, bool),
CARLA_KEY_MAIN_EXPERIMENTAL: settings.value(CARLA_KEY_MAIN_EXPERIMENTAL, CARLA_DEFAULT_MAIN_EXPERIMENTAL, bool), CARLA_KEY_MAIN_EXPERIMENTAL: settings.value(CARLA_KEY_MAIN_EXPERIMENTAL, CARLA_DEFAULT_MAIN_EXPERIMENTAL, bool),
CARLA_KEY_MAIN_SKIN_TWEAKS: settings.value(CARLA_KEY_MAIN_SKIN_TWEAKS, CARLA_DEFAULT_MAIN_SKIN_TWEAKS, str),
CARLA_KEY_CANVAS_THEME: settings.value(CARLA_KEY_CANVAS_THEME, CARLA_DEFAULT_CANVAS_THEME, str), CARLA_KEY_CANVAS_THEME: settings.value(CARLA_KEY_CANVAS_THEME, CARLA_DEFAULT_CANVAS_THEME, str),
CARLA_KEY_CANVAS_SIZE: settings.value(CARLA_KEY_CANVAS_SIZE, CARLA_DEFAULT_CANVAS_SIZE, str), CARLA_KEY_CANVAS_SIZE: settings.value(CARLA_KEY_CANVAS_SIZE, CARLA_DEFAULT_CANVAS_SIZE, str),
CARLA_KEY_CANVAS_AUTO_HIDE_GROUPS: settings.value(CARLA_KEY_CANVAS_AUTO_HIDE_GROUPS, CARLA_DEFAULT_CANVAS_AUTO_HIDE_GROUPS, bool), CARLA_KEY_CANVAS_AUTO_HIDE_GROUPS: settings.value(CARLA_KEY_CANVAS_AUTO_HIDE_GROUPS, CARLA_DEFAULT_CANVAS_AUTO_HIDE_GROUPS, bool),
@@ -2182,6 +2290,19 @@ class HostWindow(QMainWindow):
self.ui.toolBar.blockSignals(True) self.ui.toolBar.blockSignals(True)
self.ui.toolBar.setEnabled(yesNo) self.ui.toolBar.setEnabled(yesNo)
self.ui.toolBar.setVisible(yesNo) self.ui.toolBar.setVisible(yesNo)
self.ui.act_settings_show_toolbar_text.setEnabled(yesNo)
self.ui.toolBar.blockSignals(False)

@pyqtSlot(bool)
def slot_showToolbarText(self, yesNo):
self.ui.toolBar.blockSignals(True)

for btn in self.ui.toolBar.findChildren(QToolButton):
if yesNo:
btn.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
else:
btn.setToolButtonStyle(Qt.ToolButtonIconOnly)

self.ui.toolBar.blockSignals(False) self.ui.toolBar.blockSignals(False)


@pyqtSlot(bool) @pyqtSlot(bool)
@@ -2623,6 +2744,8 @@ class HostWindow(QMainWindow):
self.ui.toolBar.setEnabled(visible) self.ui.toolBar.setEnabled(visible)
self.ui.toolBar.blockSignals(False) self.ui.toolBar.blockSignals(False)
self.ui.act_settings_show_toolbar.setChecked(visible) self.ui.act_settings_show_toolbar.setChecked(visible)
self.ui.act_settings_show_toolbar_text.setEnabled(visible)



@pyqtSlot(int) @pyqtSlot(int)
def slot_tabChanged(self, index): def slot_tabChanged(self, index):
@@ -2889,7 +3012,6 @@ class HostWindow(QMainWindow):


def idleFast(self): def idleFast(self):
self.host.engine_idle() self.host.engine_idle()
self.refreshTransport()


if self.fPluginCount == 0 or self.fCurrentlyRemovingAllPlugins: if self.fPluginCount == 0 or self.fCurrentlyRemovingAllPlugins:
return return
@@ -2922,6 +3044,10 @@ class HostWindow(QMainWindow):
def idleSlow(self): def idleSlow(self):
self.getAndRefreshRuntimeInfo() self.getAndRefreshRuntimeInfo()


self.slowTimer = (self.slowTimer + 1) % 4
if (self.slowTimer == 0):
self.refreshTransport() # This one is CPU hungry. Ticket #1934

if self.fPluginCount == 0 or self.fCurrentlyRemovingAllPlugins: if self.fPluginCount == 0 or self.fCurrentlyRemovingAllPlugins:
return return


@@ -2949,6 +3075,13 @@ class HostWindow(QMainWindow):
QMainWindow.changeEvent(self, event) QMainWindow.changeEvent(self, event)


def updateStyle(self): def updateStyle(self):
loadTweaks(self)

if self.fTweaks.get('MoreSpace', 0):
self.ui.pad_left.hide()
self.ui.pad_right.hide()
return

# Rack padding images setup # Rack padding images setup
rack_imgL = QImage(":/bitmaps/rack_padding_left.png") rack_imgL = QImage(":/bitmaps/rack_padding_left.png")
rack_imgR = QImage(":/bitmaps/rack_padding_right.png") rack_imgR = QImage(":/bitmaps/rack_padding_right.png")


+ 7
- 0
source/frontend/carla_settings.py View File

@@ -47,6 +47,7 @@ from carla_backend import (
from carla_shared import ( from carla_shared import (
CARLA_KEY_MAIN_PROJECT_FOLDER, CARLA_KEY_MAIN_PROJECT_FOLDER,
CARLA_KEY_MAIN_USE_PRO_THEME, CARLA_KEY_MAIN_USE_PRO_THEME,
CARLA_KEY_MAIN_SKIN_TWEAKS,
CARLA_KEY_MAIN_PRO_THEME_COLOR, CARLA_KEY_MAIN_PRO_THEME_COLOR,
CARLA_KEY_MAIN_REFRESH_INTERVAL, CARLA_KEY_MAIN_REFRESH_INTERVAL,
CARLA_KEY_MAIN_CONFIRM_EXIT, CARLA_KEY_MAIN_CONFIRM_EXIT,
@@ -115,6 +116,7 @@ from carla_shared import (
CARLA_DEFAULT_MAIN_CLASSIC_SKIN, CARLA_DEFAULT_MAIN_CLASSIC_SKIN,
CARLA_DEFAULT_MAIN_SHOW_LOGS, CARLA_DEFAULT_MAIN_SHOW_LOGS,
CARLA_DEFAULT_MAIN_SYSTEM_ICONS, CARLA_DEFAULT_MAIN_SYSTEM_ICONS,
CARLA_DEFAULT_MAIN_SKIN_TWEAKS,
#CARLA_DEFAULT_MAIN_EXPERIMENTAL, #CARLA_DEFAULT_MAIN_EXPERIMENTAL,
CARLA_DEFAULT_CANVAS_THEME, CARLA_DEFAULT_CANVAS_THEME,
CARLA_DEFAULT_CANVAS_SIZE, CARLA_DEFAULT_CANVAS_SIZE,
@@ -705,6 +707,9 @@ class CarlaSettingsW(QDialog):
self.ui.cb_main_theme_color.findText(settings.value(CARLA_KEY_MAIN_PRO_THEME_COLOR, self.ui.cb_main_theme_color.findText(settings.value(CARLA_KEY_MAIN_PRO_THEME_COLOR,
CARLA_DEFAULT_MAIN_PRO_THEME_COLOR, str))) CARLA_DEFAULT_MAIN_PRO_THEME_COLOR, str)))


self.ui.le_main_skin_tweaks.setText(
settings.value(CARLA_KEY_MAIN_SKIN_TWEAKS, CARLA_DEFAULT_MAIN_SKIN_TWEAKS, str))

self.ui.sb_main_refresh_interval.setValue( self.ui.sb_main_refresh_interval.setValue(
settings.value(CARLA_KEY_MAIN_REFRESH_INTERVAL, CARLA_DEFAULT_MAIN_REFRESH_INTERVAL, int)) settings.value(CARLA_KEY_MAIN_REFRESH_INTERVAL, CARLA_DEFAULT_MAIN_REFRESH_INTERVAL, int))


@@ -995,6 +1000,7 @@ class CarlaSettingsW(QDialog):
settings.setValue(CARLA_KEY_MAIN_CONFIRM_EXIT, self.ui.ch_main_confirm_exit.isChecked()) settings.setValue(CARLA_KEY_MAIN_CONFIRM_EXIT, self.ui.ch_main_confirm_exit.isChecked())
settings.setValue(CARLA_KEY_MAIN_CLASSIC_SKIN, self.ui.cb_main_classic_skin_default.isChecked()) settings.setValue(CARLA_KEY_MAIN_CLASSIC_SKIN, self.ui.cb_main_classic_skin_default.isChecked())
settings.setValue(CARLA_KEY_MAIN_USE_PRO_THEME, self.ui.ch_main_theme_pro.isChecked()) settings.setValue(CARLA_KEY_MAIN_USE_PRO_THEME, self.ui.ch_main_theme_pro.isChecked())
settings.setValue(CARLA_KEY_MAIN_SKIN_TWEAKS, self.ui.le_main_skin_tweaks.text())
settings.setValue(CARLA_KEY_MAIN_PRO_THEME_COLOR, self.ui.cb_main_theme_color.currentText()) settings.setValue(CARLA_KEY_MAIN_PRO_THEME_COLOR, self.ui.cb_main_theme_color.currentText())
settings.setValue(CARLA_KEY_MAIN_REFRESH_INTERVAL, self.ui.sb_main_refresh_interval.value()) settings.setValue(CARLA_KEY_MAIN_REFRESH_INTERVAL, self.ui.sb_main_refresh_interval.value())
settings.setValue(CARLA_KEY_MAIN_SYSTEM_ICONS, self.ui.ch_main_system_icons.isChecked()) settings.setValue(CARLA_KEY_MAIN_SYSTEM_ICONS, self.ui.ch_main_system_icons.isChecked())
@@ -1193,6 +1199,7 @@ class CarlaSettingsW(QDialog):
self.ui.group_main_theme.isEnabled()) self.ui.group_main_theme.isEnabled())
self.ui.cb_main_theme_color.setCurrentIndex( self.ui.cb_main_theme_color.setCurrentIndex(
self.ui.cb_main_theme_color.findText(CARLA_DEFAULT_MAIN_PRO_THEME_COLOR)) self.ui.cb_main_theme_color.findText(CARLA_DEFAULT_MAIN_PRO_THEME_COLOR))
self.ui.le_main_skin_tweaks.setText(CARLA_DEFAULT_MAIN_SKIN_TWEAKS)
self.ui.sb_main_refresh_interval.setValue(CARLA_DEFAULT_MAIN_REFRESH_INTERVAL) self.ui.sb_main_refresh_interval.setValue(CARLA_DEFAULT_MAIN_REFRESH_INTERVAL)
self.ui.ch_main_confirm_exit.setChecked(CARLA_DEFAULT_MAIN_CONFIRM_EXIT) self.ui.ch_main_confirm_exit.setChecked(CARLA_DEFAULT_MAIN_CONFIRM_EXIT)
self.ui.cb_main_classic_skin_default(CARLA_DEFAULT_MAIN_CLASSIC_SKIN) self.ui.cb_main_classic_skin_default(CARLA_DEFAULT_MAIN_CLASSIC_SKIN)


+ 62
- 8
source/frontend/carla_shared.py View File

@@ -8,7 +8,7 @@
import os import os
import sys import sys


from math import fmod
from math import fmod, log10


# ------------------------------------------------------------------------------------------------------------ # ------------------------------------------------------------------------------------------------------------
# Imports (Signal) # Imports (Signal)
@@ -63,6 +63,9 @@ from carla_backend import (
ENGINE_TRANSPORT_MODE_JACK, ENGINE_TRANSPORT_MODE_JACK,
) )


from utils import QSafeSettings
import ast

# ------------------------------------------------------------------------------------------------------------ # ------------------------------------------------------------------------------------------------------------
# Config # Config


@@ -184,6 +187,7 @@ CANVAS_EYECANDY_SMALL = 1
CARLA_KEY_MAIN_PROJECT_FOLDER = "Main/ProjectFolder" # str CARLA_KEY_MAIN_PROJECT_FOLDER = "Main/ProjectFolder" # str
CARLA_KEY_MAIN_USE_PRO_THEME = "Main/UseProTheme" # bool CARLA_KEY_MAIN_USE_PRO_THEME = "Main/UseProTheme" # bool
CARLA_KEY_MAIN_PRO_THEME_COLOR = "Main/ProThemeColor" # str CARLA_KEY_MAIN_PRO_THEME_COLOR = "Main/ProThemeColor" # str
CARLA_KEY_MAIN_SKIN_TWEAKS = "Main/SkinTweaks" # str
CARLA_KEY_MAIN_REFRESH_INTERVAL = "Main/RefreshInterval" # int CARLA_KEY_MAIN_REFRESH_INTERVAL = "Main/RefreshInterval" # int
CARLA_KEY_MAIN_CONFIRM_EXIT = "Main/ConfirmExit" # bool CARLA_KEY_MAIN_CONFIRM_EXIT = "Main/ConfirmExit" # bool
CARLA_KEY_MAIN_CLASSIC_SKIN = "Main/ClassicSkin" # bool CARLA_KEY_MAIN_CLASSIC_SKIN = "Main/ClassicSkin" # bool
@@ -273,6 +277,7 @@ CARLA_DEFAULT_MAIN_CLASSIC_SKIN = False
CARLA_DEFAULT_MAIN_SHOW_LOGS = bool(not CARLA_OS_WIN) CARLA_DEFAULT_MAIN_SHOW_LOGS = bool(not CARLA_OS_WIN)
CARLA_DEFAULT_MAIN_SYSTEM_ICONS = False CARLA_DEFAULT_MAIN_SYSTEM_ICONS = False
CARLA_DEFAULT_MAIN_EXPERIMENTAL = False CARLA_DEFAULT_MAIN_EXPERIMENTAL = False
CARLA_DEFAULT_MAIN_SKIN_TWEAKS = "'ShowPan':0, 'ShowForth':0, 'WetVolPush':0, 'WetVolPushLed':1, 'Tooltips':0, 'MoreSpace':0, 'WetVolOnCompact':0, 'SymmetricArc':1, 'GapAuto':0, 'ColorFollow':0, 'ShortenLabels':1, 'ButtonHaveLed':1, 'ColoredNeon':1, 'HighContrast':0, 'ShowDisabled':0, 'ShowOutputs':1, 'ShowButtons':1, 'Button3Pos':1, 'TwoLineLabels':0, 'GapMin':0, 'GapMax':100, 'ColorFrom':-0.1, 'ColorSpan':0.4, 'Auto7segSize':0, 'Auto7segWidth':1, 'ShowReload':0, 'ShowPrograms':0, 'ShowMidiPrograms':0, 'ShowProgramsOnCompact':0, 'ShowMidiProgramsOnCompact':0, "


# Canvas # Canvas
CARLA_DEFAULT_CANVAS_THEME = "Modern Dark" CARLA_DEFAULT_CANVAS_THEME = "Modern Dark"
@@ -647,18 +652,22 @@ else:
# Find decimal points for a parameter, using step and stepSmall # Find decimal points for a parameter, using step and stepSmall


def countDecimalPoints(step, stepSmall): def countDecimalPoints(step, stepSmall):
if stepSmall >= 1.0:
if (stepSmall >= 1.0) or (step <= 0) or (stepSmall <= 0):
return 0 return 0
if step >= 1.0: if step >= 1.0:
return 2 return 2


count = 0
value = fmod(abs(step), 1)
while 0.0001 < value < 0.999 and count < 6:
value = fmod(value*10, 1)
count += 1
# OLD
# count = 0
# value = fmod(abs(step), 1)
# while 0.0001 < value < 0.999 and count < 6:
# value = fmod(value*10, 1)
# count += 1
#
# return count


return count
# NEW: Looks like better handling of small values.
return -int(log10(stepSmall)) + 2


# ------------------------------------------------------------------------------------------------------------ # ------------------------------------------------------------------------------------------------------------
# Check if a value is a number (float support) # Check if a value is a number (float support)
@@ -927,5 +936,50 @@ def CustomMessageBox(parent, icon, title, text,
# pylint: enable=no-value-for-parameter # pylint: enable=no-value-for-parameter
# pylint: enable=too-many-arguments # pylint: enable=too-many-arguments


# ------------------------------------------------------------------------------------------------------------
# Tweaks, in form of 'Parameter':Value or 'skinnameParameter':Value, are holds both per-rack and per-plugin fine-tuning values (tweaks).

def loadTweaks(self):
settings = QSafeSettings("falkTX", "Carla2")
skinTweaks = settings.value(CARLA_KEY_MAIN_SKIN_TWEAKS, CARLA_DEFAULT_MAIN_SKIN_TWEAKS, str)
try:
self.fTweaks = ast.literal_eval('{' + skinTweaks + '}')
except ValueError as e:
print("ERROR while parse `" + skinTweaks + "` :", e)

# ------------------------------------------------------------------------------------------------------------

def getPrefixSuffix(unit):
prefix = ""
suffix = unit.strip()

if suffix == "(coef)":
prefix = "* "
suffix = ""
else:
suffix = " " + suffix

return prefix, suffix

# ------------------------------------------------------------------------------------------------------------

def strLim(value, digits = 5):
# np.format_float_positional(value, trim='-', fractional=False, precision=digits)
result = "%.5f" % value
if "." in result:
result = result.strip("0")
if result[-1] == ".":
result = result.removesuffix(".")

if len(result) > 9:
return '{:.3e}'.format(value)
else:
return result

# ------------------------------------------------------------------------------------------------------------
# Geometry

RACK_KNOB_GAP = 5

# ------------------------------------------------------------------------------------------------------------ # ------------------------------------------------------------------------------------------------------------
# pylint: enable=possibly-used-before-assignment # pylint: enable=possibly-used-before-assignment

+ 394
- 203
source/frontend/carla_skin.py View File

@@ -6,15 +6,19 @@
# Imports (Global) # Imports (Global)


from qt_compat import qt_config from qt_compat import qt_config
import math
import random
import operator
from operator import itemgetter


if qt_config == 5: if qt_config == 5:
from PyQt5.QtCore import Qt, QRectF, QLineF, QTimer from PyQt5.QtCore import Qt, QRectF, QLineF, QTimer
from PyQt5.QtGui import QColor, QFont, QFontDatabase, QPainter, QPainterPath, QPen from PyQt5.QtGui import QColor, QFont, QFontDatabase, QPainter, QPainterPath, QPen
from PyQt5.QtWidgets import QColorDialog, QFrame, QLineEdit, QPushButton
from PyQt5.QtWidgets import QColorDialog, QFrame, QLineEdit, QPushButton, QComboBox, QSizePolicy
elif qt_config == 6: elif qt_config == 6:
from PyQt6.QtCore import Qt, QRectF, QLineF, QTimer from PyQt6.QtCore import Qt, QRectF, QLineF, QTimer
from PyQt6.QtGui import QColor, QFont, QFontDatabase, QPainter, QPainterPath, QPen from PyQt6.QtGui import QColor, QFont, QFontDatabase, QPainter, QPainterPath, QPen
from PyQt6.QtWidgets import QColorDialog, QFrame, QLineEdit, QPushButton
from PyQt6.QtWidgets import QColorDialog, QFrame, QLineEdit, QPushButton, QComboBox, QSizePolicy


# ------------------------------------------------------------------------------------------------------------ # ------------------------------------------------------------------------------------------------------------
# Imports (Custom) # Imports (Custom)
@@ -29,7 +33,6 @@ from carla_backend import *
from carla_shared import * from carla_shared import *
from carla_widgets import * from carla_widgets import *
from widgets.digitalpeakmeter import DigitalPeakMeter from widgets.digitalpeakmeter import DigitalPeakMeter
from widgets.paramspinbox import CustomInputDialog
from widgets.scalabledial import ScalableDial from widgets.scalabledial import ScalableDial


# ------------------------------------------------------------------------------------------------------------ # ------------------------------------------------------------------------------------------------------------
@@ -126,7 +129,7 @@ def getParameterShortName(paramName):
# Get RGB colors for a plugin category # Get RGB colors for a plugin category


def getColorFromCategory(category): def getColorFromCategory(category):
r = 40
r = 39
g = 40 g = 40
b = 40 b = 40


@@ -152,45 +155,35 @@ def getColorFromCategory(category):
return (r, g, b) return (r, g, b)


# ------------------------------------------------------------------------------------------------------------ # ------------------------------------------------------------------------------------------------------------
#

def setScalableDialStyle(widget, parameterId, parameterCount, whiteLabels, skinStyle):
if skinStyle.startswith("calf"):
widget.setCustomPaintMode(ScalableDial.CUSTOM_PAINT_MODE_NO_GRADIENT)
widget.setImage(7)

elif skinStyle.startswith("openav"):
widget.setCustomPaintMode(ScalableDial.CUSTOM_PAINT_MODE_NO_GRADIENT)
if parameterId == PARAMETER_DRYWET:
widget.setImage(13)
elif parameterId == PARAMETER_VOLUME:
widget.setImage(12)
else:
widget.setImage(11)

else:
if parameterId == PARAMETER_DRYWET:
widget.setCustomPaintMode(ScalableDial.CUSTOM_PAINT_MODE_CARLA_WET)

elif parameterId == PARAMETER_VOLUME:
widget.setCustomPaintMode(ScalableDial.CUSTOM_PAINT_MODE_CARLA_VOL)

else:
_r = 255 - int((float(parameterId)/float(parameterCount))*200.0)
_g = 55 + int((float(parameterId)/float(parameterCount))*200.0)
_b = 0 #(r-40)*4
widget.setCustomPaintColor(QColor(_r, _g, _b))
widget.setCustomPaintMode(ScalableDial.CUSTOM_PAINT_MODE_COLOR)

if whiteLabels:
colorEnabled = QColor("#BBB")
colorDisabled = QColor("#555")
else:
colorEnabled = QColor("#111")
colorDisabled = QColor("#AAA")


widget.setLabelColor(colorEnabled, colorDisabled)
widget.setImage(3)
skinList = [
"default",
"3bandeq",
"rncbc",
"calf_black",
"calf_blue",
"classic",
"openav-old",
"openav",
"zynfx",
"presets",
"mpresets",
"tube",
]

skinListTweakable = [
"default",
"calf",
"openav",
"zynfx",
"tube",
]

def arrayIndex(array, value):
for index, item in enumerate(array):
if item.startswith(value):
return index
return 0


# ------------------------------------------------------------------------------------------------------------ # ------------------------------------------------------------------------------------------------------------
# Abstract plugin slot # Abstract plugin slot
@@ -235,6 +228,16 @@ class AbstractPluginSlot(QFrame, PluginEditParentMeta):


self.fAdjustViewableKnobCountScheduled = False self.fAdjustViewableKnobCountScheduled = False


# load fresh skin tweaks
self.fTweaks = {}
loadTweaks(self)

# take panel color hue & sat to make "follow panel" paint
color = QColor(skinColor[0], skinColor[1], skinColor[2])
hue = color.hueF() % 1.0
sat = color.saturationF()
self.fColorHint = int(hue * 100) + int(sat * 100) / 100.0 # 50.80: 50% hue, 80% sat

# used during testing # used during testing
self.fIdleTimerId = 0 self.fIdleTimerId = 0


@@ -269,6 +272,8 @@ class AbstractPluginSlot(QFrame, PluginEditParentMeta):
self.w_knobs_right = None self.w_knobs_right = None
self.spacer_knobs = None self.spacer_knobs = None


self.slowTimer = 0

# ------------------------------------------------------------- # -------------------------------------------------------------
# Set-up connections # Set-up connections


@@ -348,8 +353,31 @@ class AbstractPluginSlot(QFrame, PluginEditParentMeta):
if self.fEditDialog is not None and self.fPluginId == pluginId: if self.fEditDialog is not None and self.fPluginId == pluginId:
self.customUiStateChanged(state) self.customUiStateChanged(state)


# @pyqtSlot(int, int, int)
# def slot_handleParameterKnobVisible(self, pluginId, index, value):
# if self.fEditDialog is not None and self.fPluginId == pluginId:
# self.setKnobVisible(index, value)

# @pyqtSlot(bool)
# def slot_knobVisible(self, value):
# self.host.set_drywet(self.fPluginId, value)
# self.setParameterValue(PARAMETER_DRYWET, value, True)

@pyqtSlot(float)
def slot_dryWetChanged(self, value):
self.host.set_drywet(self.fPluginId, value)
self.setParameterValue(PARAMETER_DRYWET, value, True)

@pyqtSlot(float)
def slot_volumeChanged(self, value):
self.host.set_volume(self.fPluginId, value)
self.setParameterValue(PARAMETER_VOLUME, value, True)

# ------------------------------------------------------------------ # ------------------------------------------------------------------


def tweak(self, skinName, tweakName, default):
return self.fTweaks.get(skinName + tweakName, self.fTweaks.get(tweakName, default))

def ready(self): def ready(self):
self.fIsActive = bool(self.host.get_internal_parameter_value(self.fPluginId, PARAMETER_ACTIVE) >= 0.5) self.fIsActive = bool(self.host.get_internal_parameter_value(self.fPluginId, PARAMETER_ACTIVE) >= 0.5)


@@ -481,6 +509,9 @@ class AbstractPluginSlot(QFrame, PluginEditParentMeta):
self.peak_in.setMeterStyle(DigitalPeakMeter.STYLE_RNCBC) self.peak_in.setMeterStyle(DigitalPeakMeter.STYLE_RNCBC)
elif self.fSkinStyle.startswith("openav") or self.fSkinStyle == "zynfx": elif self.fSkinStyle.startswith("openav") or self.fSkinStyle == "zynfx":
self.peak_in.setMeterStyle(DigitalPeakMeter.STYLE_OPENAV) self.peak_in.setMeterStyle(DigitalPeakMeter.STYLE_OPENAV)
elif self.fSkinStyle == "tube":
self.peak_in.setMeterStyle(DigitalPeakMeter.STYLE_TUBE)
self.peak_in.setMeterLinesEnabled(False)


if self.fPeaksInputCount == 0 and not isinstance(self, PluginSlot_Classic): if self.fPeaksInputCount == 0 and not isinstance(self, PluginSlot_Classic):
self.peak_in.hide() self.peak_in.hide()
@@ -496,6 +527,9 @@ class AbstractPluginSlot(QFrame, PluginEditParentMeta):
self.peak_out.setMeterStyle(DigitalPeakMeter.STYLE_RNCBC) self.peak_out.setMeterStyle(DigitalPeakMeter.STYLE_RNCBC)
elif self.fSkinStyle.startswith("openav") or self.fSkinStyle == "zynfx": elif self.fSkinStyle.startswith("openav") or self.fSkinStyle == "zynfx":
self.peak_out.setMeterStyle(DigitalPeakMeter.STYLE_OPENAV) self.peak_out.setMeterStyle(DigitalPeakMeter.STYLE_OPENAV)
elif self.fSkinStyle == "tube":
self.peak_out.setMeterStyle(DigitalPeakMeter.STYLE_TUBE)
self.peak_out.setMeterLinesEnabled(False)


if self.fPeaksOutputCount == 0 and not isinstance(self, PluginSlot_Classic): if self.fPeaksOutputCount == 0 and not isinstance(self, PluginSlot_Classic):
self.peak_out.hide() self.peak_out.hide()
@@ -530,7 +564,8 @@ class AbstractPluginSlot(QFrame, PluginEditParentMeta):
styleSheet2 = "background-image: url(:/bitmaps/background_%s.png);" % self.fSkinStyle styleSheet2 = "background-image: url(:/bitmaps/background_%s.png);" % self.fSkinStyle
else: else:
styleSheet2 = "background-color: rgb(200, 200, 200);" styleSheet2 = "background-color: rgb(200, 200, 200);"
styleSheet2 += "background-image: url(:/bitmaps/background_noise1.png);"
if self.fSkinStyle not in ("classic"):
styleSheet2 += "background-image: url(:/bitmaps/background_noise1.png);"


if not self.fDarkStyle: if not self.fDarkStyle:
colorEnabled = "#111" colorEnabled = "#111"
@@ -551,12 +586,63 @@ class AbstractPluginSlot(QFrame, PluginEditParentMeta):


styleSheet += """ styleSheet += """
QComboBox#cb_presets, QComboBox#cb_presets,
QComboBox#cb_presets0,
QComboBox#cb_presets1,
QLabel#label_audio_in, QLabel#label_audio_in,
QLabel#label_audio_out, QLabel#label_audio_out,
QLabel#label_midi { font-size: 10px; } QLabel#label_midi { font-size: 10px; }
""" """
self.setStyleSheet(styleSheet) self.setStyleSheet(styleSheet)


# -------------------------------------------------------------
# Wet and Vol knobs on compacted slot

# If "long" style not in "shorts" list, it will be matched to 0 ("default").
skinNum = arrayIndex(skinListTweakable, self.fSkinStyle[: 3])
skinName = skinListTweakable [skinNum]

wetVolOnCompact = self.tweak(skinName, 'WetVolOnCompact', 0)
showDisabled = self.tweak(skinName, 'ShowDisabled', 0)
showOutputs = self.tweak(skinName, 'ShowOutputs', 0)
shortenLabels = self.tweak(skinName, 'ShortenLabels', 1)
btn3state = self.tweak(skinName, 'Button3Pos', 1)

if isinstance(self, PluginSlot_Compact):
if self.fPluginInfo['hints'] & PLUGIN_CAN_DRYWET or showDisabled:


self.dial0 = ScalableDial(self, PARAMETER_DRYWET, 100, 1.0, 0.0, 1.0, "Dry/Wet", ScalableDial.CUSTOM_PAINT_MODE_CARLA_WET_MINI, -1, "%", self.fSkinStyle, whiteLabels, self.fTweaks)
self.dial0.setObjectName("dial0")
self.ui.horizontalLayout_2.insertWidget(6, self.dial0)

if wetVolOnCompact:
self.dial0.setEnabled(bool(self.fPluginInfo['hints'] & PLUGIN_CAN_DRYWET))
self.dial0.dragStateChanged.connect(self.slot_parameterDragStateChanged)
self.dial0.realValueChanged.connect(self.slot_dryWetChanged)
self.dial0.customContextMenuRequested.connect(self.slot_knobCustomMenu)
self.dial0.blockSignals(True)
self.dial0.setValue(self.host.get_internal_parameter_value(self.fPluginId, PARAMETER_DRYWET))
self.dial0.blockSignals(False)
else:
self.dial0.hide()

if self.fPluginInfo['hints'] & PLUGIN_CAN_VOLUME or showDisabled:
# self.dial1 = ScalableDial(self.ui.dial1, PARAMETER_VOLUME, 254, 1.0, 0.0, 1.27, "Volume", ScalableDial.CUSTOM_PAINT_MODE_CARLA_VOL_MINI, 0, "%", self.fSkinStyle, whiteLabels, self.fTweaks)
self.dial1 = ScalableDial(self, PARAMETER_VOLUME, 127, 1.0, 0.0, 1.27, "Volume", ScalableDial.CUSTOM_PAINT_MODE_CARLA_VOL_MINI, -1, "%", self.fSkinStyle, whiteLabels, self.fTweaks)
self.dial1.setObjectName("dial1")
self.ui.horizontalLayout_2.insertWidget(6, self.dial1)

if wetVolOnCompact:
self.dial1.setEnabled(bool(self.fPluginInfo['hints'] & PLUGIN_CAN_VOLUME))
self.dial1.dragStateChanged.connect(self.slot_parameterDragStateChanged)
self.dial1.realValueChanged.connect(self.slot_volumeChanged)
self.dial1.customContextMenuRequested.connect(self.slot_knobCustomMenu)
self.dial1.blockSignals(True)
self.dial1.setValue(self.host.get_internal_parameter_value(self.fPluginId, PARAMETER_VOLUME))
self.dial1.blockSignals(False)
else:
self.dial1.hide()

# ------------------------------------------------------------- # -------------------------------------------------------------
# Set-up parameters # Set-up parameters


@@ -565,6 +651,11 @@ class AbstractPluginSlot(QFrame, PluginEditParentMeta):


index = 0 index = 0
layout = self.w_knobs_left.layout() layout = self.w_knobs_left.layout()

# Rainbow paint, default is deep red -> green. Span can be negative.
hueFrom = self.tweak(skinName, 'ColorFrom', -0.03)
hueSpan = self.tweak(skinName, 'ColorSpan', 0.4)

for i in range(parameterCount): for i in range(parameterCount):
# 50 should be enough for everybody, right? # 50 should be enough for everybody, right?
if index >= 50: if index >= 50:
@@ -573,64 +664,145 @@ class AbstractPluginSlot(QFrame, PluginEditParentMeta):
paramInfo = self.host.get_parameter_info(self.fPluginId, i) paramInfo = self.host.get_parameter_info(self.fPluginId, i)
paramData = self.host.get_parameter_data(self.fPluginId, i) paramData = self.host.get_parameter_data(self.fPluginId, i)
paramRanges = self.host.get_parameter_ranges(self.fPluginId, i) paramRanges = self.host.get_parameter_ranges(self.fPluginId, i)
isInteger = (paramData['hints'] & PARAMETER_IS_INTEGER) != 0
default = self.host.get_default_parameter_value(self.fPluginId, i)
minimum = paramRanges['min']
maximum = paramRanges['max']
isEnabled = (paramData['hints'] & PARAMETER_IS_ENABLED) != 0
isOutput = (paramData['type'] != PARAMETER_INPUT)
isBoolean = (paramData['hints'] & PARAMETER_IS_BOOLEAN) != 0
isInteger = ((paramData['hints'] & PARAMETER_IS_INTEGER) != 0) or isBoolean


if paramData['type'] != PARAMETER_INPUT:
continue
if paramData['hints'] & PARAMETER_IS_BOOLEAN:
continue
if (paramData['hints'] & PARAMETER_IS_ENABLED) == 0:
continue
if (paramData['hints'] & PARAMETER_USES_SCALEPOINTS) != 0 and not isInteger:
# NOTE: we assume integer scalepoints are continuous
continue
if isInteger and paramRanges['max']-paramRanges['min'] <= 3:
continue
if paramInfo['name'].startswith("unused"): if paramInfo['name'].startswith("unused"):
print("Carla: INFO: Parameter "+str(i)+" is Unused, so skipped.")
continue continue


paramName = getParameterShortName(paramInfo['name'])
if not isEnabled:
print("Carla: INFO: Parameter "+str(i)+" is Disabled.")
if not showDisabled:
continue

delta = maximum - minimum
if delta <= 0:
print("Carla: ERROR: Parameter "+str(i)+": Min, Max are same or wrong.")
return

# NOTE: Booleans are mimic as isInteger with range [0 or 1].
if btn3state:
isButton = (isInteger and (minimum == 0) and (maximum in (1, 2)))
else:
isButton = (isInteger and (minimum == 0) and (maximum == 1))

vuMeter = 0
precision = 1
if isOutput:
if not showOutputs:
continue
vuMeter = ((minimum == 0) and ((maximum == 1) or (maximum == 100)))\
or (minimum == -maximum) # from -N to N, is it good to use VU ?
else:
# Integers have somewhat more coarse step
if isInteger:
while delta > 50:
delta = int(math.ceil(delta / 2))
precision = delta

# Floats are finer-step smoothed
else:
# Pretty steps for most common values, like 1-2-5-10 scales,
# still not in its final form.
while delta > 200:
# Mantissa is near 2.5
is25 = int(abs((log10(delta) % 1) - log10(2.5)) < 0.001)
delta = delta / (2.0 + is25 * 0.5)

while delta < 100:
# Mantissa is near 2.0
is25 = int(abs((log10(delta) % 1) - log10(2.0)) < 0.001)
delta = delta * (2.0 + is25 * 0.5)

precision = math.ceil(delta)

if precision <= 0: # suddenly...
print("Carla: ERROR: Parameter "+str(i)+": Precision "+str(precision)+" is wrong!")
return

if shortenLabels:
label = getParameterShortName(paramInfo['name'])
else:
label = paramInfo['name']

widget = ScalableDial(self, i,
precision,
default,
minimum,
maximum,
label,
skinNum * 16,
self.fColorHint,
paramInfo['unit'],
self.fSkinStyle,
whiteLabels,
self.fTweaks,
isInteger,
isButton,
isOutput,
vuMeter,
1 ) # isVisible Experiment (index % 2)

widget.setEnabled(isEnabled)


widget = ScalableDial(self, i)
widget.setLabel(paramName)
widget.setMinimum(paramRanges['min'])
widget.setMaximum(paramRanges['max'])
widget.hide() widget.hide()


if isInteger:
widget.setPrecision(paramRanges['max']-paramRanges['min'], True)
scalePoints = []
prefix = ""
suffix = ""
# NOTE: Issue #1983
# if ((paramData['hints'] & PARAMETER_USES_SCALEPOINTS) != 0):
count = paramInfo['scalePointCount']
if count:
for j in range(count):
scalePoints.append(self.host.get_parameter_scalepoint_info(self.fPluginId, i, j))


setScalableDialStyle(widget, i, parameterCount, whiteLabels, self.fSkinStyle)
prefix, suffix = getPrefixSuffix(paramInfo['unit'])
widget.setScalePPS(sorted(scalePoints, key=operator.itemgetter("value")), prefix, suffix)


index += 1 index += 1
self.fParameterList.append([i, widget]) self.fParameterList.append([i, widget])
layout.addWidget(widget) layout.addWidget(widget)


if self.w_knobs_right is not None and (self.fPluginInfo['hints'] & PLUGIN_CAN_DRYWET) != 0:
widget = ScalableDial(self, PARAMETER_DRYWET)
widget.setLabel("Dry/Wet")
widget.setMinimum(0.0)
widget.setMaximum(1.0)
setScalableDialStyle(widget, PARAMETER_DRYWET, 0, whiteLabels, self.fSkinStyle)
for i in range(index):
widget = layout.itemAt(i).widget()
if widget is not None:
coef = i/(index-1) if index > 1 else 0.5 # 0.5 = Midrange
hue = (hueFrom + coef * hueSpan) % 1.0
widget.setCustomPaintColor(QColor.fromHslF(hue, 1, 0.5, 1))


self.fParameterList.append([PARAMETER_DRYWET, widget])
self.w_knobs_right.layout().addWidget(widget)


if self.w_knobs_right is not None and (self.fPluginInfo['hints'] & PLUGIN_CAN_VOLUME) != 0:
widget = ScalableDial(self, PARAMETER_VOLUME)
widget.setLabel("Volume")
widget.setMinimum(0.0)
widget.setMaximum(1.27)
setScalableDialStyle(widget, PARAMETER_VOLUME, 0, whiteLabels, self.fSkinStyle)
if self.w_knobs_right is not None:
if (self.fPluginInfo['hints'] & PLUGIN_CAN_DRYWET) != 0:
widget = ScalableDial(self, PARAMETER_DRYWET, 100, 1.0, 0.0, 1.0, "Dry/Wet", skinNum * 16 + ScalableDial.CUSTOM_PAINT_MODE_CARLA_WET, -1, "%", self.fSkinStyle, whiteLabels, self.fTweaks)


self.fParameterList.append([PARAMETER_VOLUME, widget])
self.w_knobs_right.layout().addWidget(widget)
self.fParameterList.append([PARAMETER_DRYWET, widget])
self.w_knobs_right.layout().addWidget(widget)

if (self.fPluginInfo['hints'] & PLUGIN_CAN_VOLUME) != 0:
widget = ScalableDial(self, PARAMETER_VOLUME, 127, 1.0, 0.0, 1.27, "Volume", skinNum * 16 + ScalableDial.CUSTOM_PAINT_MODE_CARLA_VOL, -1, "%", self.fSkinStyle, whiteLabels, self.fTweaks)

self.fParameterList.append([PARAMETER_VOLUME, widget])
self.w_knobs_right.layout().addWidget(widget)

if (self.fPluginInfo['hints'] & PLUGIN_CAN_PANNING) != 0:
if widget.getTweak('ShowPan', 0):
widget = ScalableDial(self, PARAMETER_PANNING, 100, 0.0, -1.0, 1.0, "Pan", skinNum * 16 + ScalableDial.CUSTOM_PAINT_MODE_CARLA_PAN, -1, "%", self.fSkinStyle, whiteLabels, self.fTweaks)

self.fParameterList.append([PARAMETER_PANNING, widget])
self.w_knobs_right.layout().addWidget(widget)


for paramIndex, paramWidget in self.fParameterList: for paramIndex, paramWidget in self.fParameterList:
paramWidget.setContextMenuPolicy(Qt.CustomContextMenu)
paramWidget.customContextMenuRequested.connect(self.slot_knobCustomMenu)
paramWidget.dragStateChanged.connect(self.slot_parameterDragStateChanged)
paramWidget.realValueChanged.connect(self.slot_parameterValueChanged)
if not paramWidget.fIsOutput:
paramWidget.customContextMenuRequested.connect(self.slot_knobCustomMenu)
paramWidget.dragStateChanged.connect(self.slot_parameterDragStateChanged)
paramWidget.realValueChanged.connect(self.slot_parameterValueChanged)
paramWidget.blockSignals(True) paramWidget.blockSignals(True)
paramWidget.setValue(self.host.get_internal_parameter_value(self.fPluginId, paramIndex)) paramWidget.setValue(self.host.get_internal_parameter_value(self.fPluginId, paramIndex))
paramWidget.blockSignals(False) paramWidget.blockSignals(False)
@@ -724,6 +896,7 @@ class AbstractPluginSlot(QFrame, PluginEditParentMeta):
elif parameterId == PARAMETER_CTRL_CHANNEL: elif parameterId == PARAMETER_CTRL_CHANNEL:
self.host.set_ctrl_channel(self.fPluginId, value) self.host.set_ctrl_channel(self.fPluginId, value)


self.setParameterValue(parameterId, value, True)
self.fEditDialog.setParameterValue(parameterId, value) self.fEditDialog.setParameterValue(parameterId, value)


# ----------------------------------------------------------------- # -----------------------------------------------------------------
@@ -891,13 +1064,18 @@ class AbstractPluginSlot(QFrame, PluginEditParentMeta):
paramWidget.setVisible(hints & PLUGIN_CAN_DRYWET) paramWidget.setVisible(hints & PLUGIN_CAN_DRYWET)
elif paramIndex == PARAMETER_VOLUME: elif paramIndex == PARAMETER_VOLUME:
paramWidget.setVisible(hints & PLUGIN_CAN_VOLUME) paramWidget.setVisible(hints & PLUGIN_CAN_VOLUME)
# jpka: FIXME i add it, but can't trigger it for test, so disable to prevent possible crashes. Maybe it don't needed.
# self.dial0.setVisible(hints & PLUGIN_CAN_DRYWET)
# self.dial1.setVisible(hints & PLUGIN_CAN_VOLUME)
# print("self.dial0.setVisible(hints & PLUGIN_CAN_DRYWET)")


if self.b_gui is not None: if self.b_gui is not None:
self.b_gui.setEnabled(bool(hints & PLUGIN_HAS_CUSTOM_UI)) self.b_gui.setEnabled(bool(hints & PLUGIN_HAS_CUSTOM_UI))


# NOTE: self.fParameterList is empty when compacted.
def editDialogParameterValueChanged(self, pluginId, parameterId, value): def editDialogParameterValueChanged(self, pluginId, parameterId, value):
for paramIndex, paramWidget in self.fParameterList: for paramIndex, paramWidget in self.fParameterList:
if paramIndex != parameterId:
if (paramIndex != parameterId) or paramWidget.fIsOutput:
continue continue


paramWidget.blockSignals(True) paramWidget.blockSignals(True)
@@ -905,6 +1083,16 @@ class AbstractPluginSlot(QFrame, PluginEditParentMeta):
paramWidget.blockSignals(False) paramWidget.blockSignals(False)
break break


if isinstance(self, PluginSlot_Compact):
if (parameterId == PARAMETER_DRYWET) and (self.fPluginInfo['hints'] & PLUGIN_CAN_DRYWET):
self.dial0.blockSignals(True)
self.dial0.setValue(value)
self.dial0.blockSignals(False)
if (parameterId == PARAMETER_VOLUME) and (self.fPluginInfo['hints'] & PLUGIN_CAN_VOLUME):
self.dial1.blockSignals(True)
self.dial1.setValue(value)
self.dial1.blockSignals(False)

def editDialogProgramChanged(self, pluginId, index): def editDialogProgramChanged(self, pluginId, index):
if self.cb_presets is None: if self.cb_presets is None:
return return
@@ -997,6 +1185,23 @@ class AbstractPluginSlot(QFrame, PluginEditParentMeta):


self.fEditDialog.idleSlow() self.fEditDialog.idleSlow()


# 7-seg displays are pretty effective, but added frame skip will make it even better.
self.slowTimer = (self.slowTimer + 1) % 2 # Half the FPS, win some CPU.

# NOTE: self.fParameterList is empty when compacted.
for paramIndex, paramWidget in self.fParameterList:
if not paramWidget.fIsOutput:
continue
# VU displays are CPU effective, make it run faster than 7-seg displays.
# if (self.slowTimer > 0) and (not paramWidget.fIsVuOutput):
if (self.slowTimer > 0):
continue

paramWidget.blockSignals(True) # TODO Is it required for output?
value = self.host.get_current_parameter_value(self.fPluginId, paramIndex)
paramWidget.setValue(value, False)
paramWidget.blockSignals(False)

# ----------------------------------------------------------------- # -----------------------------------------------------------------


def drawOutline(self, painter): def drawOutline(self, painter):
@@ -1023,7 +1228,9 @@ class AbstractPluginSlot(QFrame, PluginEditParentMeta):


def updateParameterValues(self): def updateParameterValues(self):
for paramIndex, paramWidget in self.fParameterList: for paramIndex, paramWidget in self.fParameterList:
if paramIndex < 0:
if paramIndex < 0: # DryWet and Volume
continue
if paramWidget.fIsOutput:
continue continue


paramWidget.blockSignals(True) paramWidget.blockSignals(True)
@@ -1044,8 +1251,9 @@ class AbstractPluginSlot(QFrame, PluginEditParentMeta):
# Expand/Minimize and Tweaks # Expand/Minimize and Tweaks


actCompact = menu.addAction(self.tr("Expand") if isinstance(self, PluginSlot_Compact) else self.tr("Minimize")) actCompact = menu.addAction(self.tr("Expand") if isinstance(self, PluginSlot_Compact) else self.tr("Minimize"))
actColor = menu.addAction(self.tr("Change Color..."))
actSkin = menu.addAction(self.tr("Change Skin..."))
actColor = menu.addAction(self.tr("Change Color..."))
actColorRandom = menu.addAction(self.tr("Random Color"))
actSkin = menu.addAction(self.tr("Change Skin..."))
menu.addSeparator() menu.addSeparator()


# ------------------------------------------------------------- # -------------------------------------------------------------
@@ -1157,28 +1365,17 @@ class AbstractPluginSlot(QFrame, PluginEditParentMeta):
colorStr = "%i;%i;%i" % color colorStr = "%i;%i;%i" % color
gCarla.gui.changePluginColor(self.fPluginId, color, colorStr) gCarla.gui.changePluginColor(self.fPluginId, color, colorStr)


elif actSel == actSkin:
skinList = [
"default",
"3bandeq",
"rncbc",
"calf_black",
"calf_blue",
"classic",
"openav-old",
"openav",
"zynfx",
"presets",
"mpresets",
]
try:
index = skinList.index(self.fSkinStyle)
except:
index = 0
elif actSel == actColorRandom:
hue = QColor(self.fSkinColor[0], self.fSkinColor[1], self.fSkinColor[2]).hueF()
color = QColor.fromHslF((hue + random.random()*0.5 + 0.25) % 1.0, 0.25, 0.125, 1).getRgb()[0:3]
colorStr = "%i;%i;%i" % color
gCarla.gui.changePluginColor(self.fPluginId, color, colorStr)


elif actSel == actSkin:
skin = QInputDialog.getItem(self, self.tr("Change Skin"), skin = QInputDialog.getItem(self, self.tr("Change Skin"),
self.tr("Change Skin to:"), self.tr("Change Skin to:"),
skinList, index, False)
skinList, arrayIndex(skinList, self.fSkinStyle), False)

if not all(skin): if not all(skin):
return return
gCarla.gui.changePluginSkin(self.fPluginId, skin[0]) gCarla.gui.changePluginSkin(self.fPluginId, skin[0])
@@ -1287,97 +1484,7 @@ class AbstractPluginSlot(QFrame, PluginEditParentMeta):


@pyqtSlot() @pyqtSlot()
def slot_knobCustomMenu(self): def slot_knobCustomMenu(self):
sender = self.sender()
index = sender.fIndex
minimum = sender.fMinimum
maximum = sender.fMaximum
current = sender.fRealValue
label = sender.fLabel

if index in (PARAMETER_NULL, PARAMETER_CTRL_CHANNEL) or index <= PARAMETER_MAX:
return
elif index in (PARAMETER_DRYWET, PARAMETER_VOLUME):
default = 1.0
elif index == PARAMETER_BALANCE_LEFT:
default = -1.0
elif index == PARAMETER_BALANCE_RIGHT:
default = 1.0
elif index == PARAMETER_PANNING:
default = 0.0
else:
default = self.host.get_default_parameter_value(self.fPluginId, index)

if index < PARAMETER_NULL:
# show in integer percentage
textReset = self.tr("Reset (%i%%)" % round(default*100.0))
textMinim = self.tr("Set to Minimum (%i%%)" % round(minimum*100.0))
textMaxim = self.tr("Set to Maximum (%i%%)" % round(maximum*100.0))
else:
# show in full float value
textReset = self.tr("Reset (%f)" % default)
textMinim = self.tr("Set to Minimum (%f)" % minimum)
textMaxim = self.tr("Set to Maximum (%f)" % maximum)

menu = QMenu(self)
actReset = menu.addAction(textReset)
menu.addSeparator()
actMinimum = menu.addAction(textMinim)
actCenter = menu.addAction(self.tr("Set to Center"))
actMaximum = menu.addAction(textMaxim)
menu.addSeparator()
actSet = menu.addAction(self.tr("Set value..."))

if index > PARAMETER_NULL or index not in (PARAMETER_BALANCE_LEFT, PARAMETER_BALANCE_RIGHT, PARAMETER_PANNING):
menu.removeAction(actCenter)

actSelected = menu.exec_(QCursor.pos())

if actSelected == actSet:
if index < PARAMETER_NULL:
value, ok = QInputDialog.getInt(self, self.tr("Set value"), label, round(current*100), round(minimum*100), round(maximum*100), 1)

if not ok:
return

value = float(value)/100.0

else:
paramInfo = self.host.get_parameter_info(self.fPluginId, index)
paramRanges = self.host.get_parameter_ranges(self.fPluginId, index)
scalePoints = []

for i in range(paramInfo['scalePointCount']):
scalePoints.append(self.host.get_parameter_scalepoint_info(self.fPluginId, index, i))

prefix = ""
suffix = paramInfo['unit'].strip()

if suffix == "(coef)":
prefix = "* "
suffix = ""
else:
suffix = " " + suffix

dialog = CustomInputDialog(self, label, current, minimum, maximum,
paramRanges['step'], paramRanges['stepSmall'], scalePoints, prefix, suffix)

if not dialog.exec_():
return

value = dialog.returnValue()

elif actSelected == actMinimum:
value = minimum
elif actSelected == actMaximum:
value = maximum
elif actSelected == actReset:
value = default
elif actSelected == actCenter:
value = 0.0
else:
return

sender.setValue(value, True)
PluginEdit.slot_knobCustomMenu(self)


# ----------------------------------------------------------------- # -----------------------------------------------------------------


@@ -1439,9 +1546,23 @@ class AbstractPluginSlot(QFrame, PluginEditParentMeta):
if index < 0: if index < 0:
break break


curWidth += widget.width() + 4
if not widget.getIsVisible():
continue

curWidth += widget.width() + RACK_KNOB_GAP


if curWidth + widget.width() * 2 + 8 < maxWidth:
if self.fTweaks.get('MoreSpace', 0):
if self.w_knobs_right is None: # calf
limit = curWidth
else:
if QT_VERSION < 0x60000:
limit = curWidth + self.w_knobs_right.getContentsMargins()[0] + 8
else:
limit = curWidth + 4 + 8
else:
limit = curWidth + 56 + 8

if limit < maxWidth:
#if not widget.isVisible(): #if not widget.isVisible():
widget.show() widget.show()
continue continue
@@ -1719,6 +1840,12 @@ class PluginSlot_Compact(AbstractPluginSlot):
self.peak_in = self.ui.peak_in self.peak_in = self.ui.peak_in
self.peak_out = self.ui.peak_out self.peak_out = self.ui.peak_out


if self.fTweaks.get('ShowProgramsOnCompact', 0):
insertProgramList(self, self.ui.layout_peaks, 0)

if self.fTweaks.get('ShowMidiProgramsOnCompact', 0):
insertMidiProgramList(self, self.ui.layout_peaks, 0)

self.ready() self.ready()


# ----------------------------------------------------------------- # -----------------------------------------------------------------
@@ -1756,11 +1883,24 @@ class PluginSlot_Default(AbstractPluginSlot):
self.w_knobs_right = self.ui.w_knobs_right self.w_knobs_right = self.ui.w_knobs_right
self.spacer_knobs = self.ui.layout_bottom.itemAt(1).spacerItem() self.spacer_knobs = self.ui.layout_bottom.itemAt(1).spacerItem()


if self.fTweaks.get('MoreSpace', 0):
self.ui.layout_bottom.setContentsMargins(0, 4, 0, 0)

if self.fTweaks.get('ShowPrograms', 0):
# insertProgramList(self, self.ui.layout_top, 6)
insertProgramList(self, self.ui.layout_peaks, 0)

if self.fTweaks.get('ShowMidiPrograms', 0):
# insertMidiProgramList(self, self.ui.layout_top, 6)
insertMidiProgramList(self, self.ui.layout_peaks, 0)

self.ready() self.ready()


# ----------------------------------------------------------------- # -----------------------------------------------------------------


def getFixedHeight(self): def getFixedHeight(self):
if self.fSkinStyle == "tube":
return 98
return 80 return 80


# ----------------------------------------------------------------- # -----------------------------------------------------------------
@@ -1839,13 +1979,17 @@ class PluginSlot_Presets(AbstractPluginSlot):
self.peak_in = self.ui.peak_in self.peak_in = self.ui.peak_in
self.peak_out = self.ui.peak_out self.peak_out = self.ui.peak_out


if skinStyle == "zynfx":
# if skinStyle == "zynfx": # TODO jpka: TEST ing zynfx as normal tweakable skin
if False:
self.setupZynFxParams() self.setupZynFxParams()
else: else:
self.w_knobs_left = self.ui.w_knobs_left self.w_knobs_left = self.ui.w_knobs_left
self.w_knobs_right = self.ui.w_knobs_right self.w_knobs_right = self.ui.w_knobs_right
self.spacer_knobs = self.ui.layout_bottom.itemAt(1).spacerItem() self.spacer_knobs = self.ui.layout_bottom.itemAt(1).spacerItem()


if self.fTweaks.get('MoreSpace', 0):
self.ui.layout_bottom.setContentsMargins(0, 4, 0, 0)

self.ready() self.ready()


if usingMidiPrograms: if usingMidiPrograms:
@@ -1855,6 +1999,8 @@ class PluginSlot_Presets(AbstractPluginSlot):


# ------------------------------------------------------------- # -------------------------------------------------------------


# it works only for internal zyn builds, which are disabled by default
# (?) not for just manual "zynfx" skin selection
def setupZynFxParams(self): def setupZynFxParams(self):
parameterCount = min(self.host.get_parameter_count(self.fPluginId), 8) parameterCount = min(self.host.get_parameter_count(self.fPluginId), 8)


@@ -2001,6 +2147,46 @@ class PluginSlot_Presets(AbstractPluginSlot):


# ------------------------------------------------------------------------------------------------------------ # ------------------------------------------------------------------------------------------------------------


def insertProgramList(self, layout, index):
count = self.host.get_program_count(self.fPluginId)
if count:
cb = QComboBox(None)
cb.setObjectName("cb_presets0") # use this stylesheet

for i in range(count):
string = self.host.get_program_name(self.fPluginId, i)

if len(string) == 0:
print("Carla: WARNING: Program List have zero length item.")
return

cb.addItem(string)

layout.insertWidget(index, cb)
cb.setCurrentIndex(self.host.get_current_program_index(self.fPluginId))
cb.currentIndexChanged.connect(self.slot_programChanged)

def insertMidiProgramList(self, layout, index):
count = self.host.get_midi_program_count(self.fPluginId)
if count:
cb = QComboBox(None)
cb.setObjectName("cb_presets1") # use this stylesheet

for i in range(count):
string = self.host.get_midi_program_data(self.fPluginId, i)['name']

if len(string) == 0:
print("Carla: WARNING: MIDI Program List have zero length item.")
return

cb.addItem(string)

layout.insertWidget(index, cb)
cb.setCurrentIndex(self.host.get_current_midi_program_index(self.fPluginId))
cb.currentIndexChanged.connect(self.slot_midiProgramChanged)

# ------------------------------------------------------------------------------------------------------------

def getColorAndSkinStyle(host, pluginId): def getColorAndSkinStyle(host, pluginId):
pluginInfo = host.get_plugin_info(pluginId) pluginInfo = host.get_plugin_info(pluginId)
pluginName = host.get_real_plugin_name(pluginId) pluginName = host.get_real_plugin_name(pluginId)
@@ -2018,9 +2204,9 @@ def getColorAndSkinStyle(host, pluginId):


# Samplers # Samplers
if pluginInfo['type'] == PLUGIN_SF2: if pluginInfo['type'] == PLUGIN_SF2:
return (colorCategory, "sf2")
return (colorCategory, "mpresets")
if pluginInfo['type'] == PLUGIN_SFZ: if pluginInfo['type'] == PLUGIN_SFZ:
return (colorCategory, "sfz")
return (colorCategory, "mpresets")


# Calf # Calf
if pluginName.split(" ", 1)[0].lower() == "calf": if pluginName.split(" ", 1)[0].lower() == "calf":
@@ -2032,6 +2218,10 @@ def getColorAndSkinStyle(host, pluginId):
if pluginMaker == "OpenAV": if pluginMaker == "OpenAV":
return (colorNone, "openav") return (colorNone, "openav")


# Tube
if "tube" in pluginLabel:
return (colorCategory, "tube")

# ZynFX # ZynFX
if pluginInfo['type'] == PLUGIN_INTERNAL: if pluginInfo['type'] == PLUGIN_INTERNAL:
if pluginLabel.startswith("zyn") and pluginInfo['category'] != PLUGIN_CATEGORY_SYNTH: if pluginLabel.startswith("zyn") and pluginInfo['category'] != PLUGIN_CATEGORY_SYNTH:
@@ -2042,7 +2232,8 @@ def getColorAndSkinStyle(host, pluginId):
return (colorNone, "zynfx") return (colorNone, "zynfx")


if pluginInfo['type'] == PLUGIN_LV2: if pluginInfo['type'] == PLUGIN_LV2:
if pluginLabel.startswith("http://kxstudio.sf.net/carla/plugins/zyn") and pluginName != "ZynAddSubFX":
# if pluginLabel.startswith("http://kxstudio.sf.net/carla/plugins/zyn") and pluginName != "ZynAddSubFX":
if pluginLabel.startswith("http://kxstudio.sf.net/carla/plugins/zyn") and pluginName != "ZynAddSubFX" or "zyn" in pluginLabel: # jpka: TEST ing zynfx as normal tweakable skin
return (colorNone, "zynfx") return (colorNone, "zynfx")


# Presets # Presets


+ 227
- 82
source/frontend/carla_widgets.py View File

@@ -14,7 +14,7 @@ from qt_compat import qt_config


if qt_config == 5: if qt_config == 5:
from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QByteArray from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QByteArray
from PyQt5.QtGui import QCursor, QIcon, QPalette, QPixmap
from PyQt5.QtGui import QCursor, QIcon, QPalette, QPixmap, QFont
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QDialog, QDialog,
QFileDialog, QFileDialog,
@@ -24,10 +24,18 @@ if qt_config == 5:
QScrollArea, QScrollArea,
QVBoxLayout, QVBoxLayout,
QWidget, QWidget,
QGraphicsScene,
QGraphicsTextItem,
QGraphicsView,
QTableWidget,
QTableWidgetItem,
QHeaderView,
QLabel,
QSizePolicy,
) )
elif qt_config == 6: elif qt_config == 6:
from PyQt6.QtCore import pyqtSignal, pyqtSlot, Qt, QByteArray from PyQt6.QtCore import pyqtSignal, pyqtSlot, Qt, QByteArray
from PyQt6.QtGui import QCursor, QIcon, QPalette, QPixmap
from PyQt6.QtGui import QCursor, QIcon, QPalette, QPixmap, QFont
from PyQt6.QtWidgets import ( from PyQt6.QtWidgets import (
QDialog, QDialog,
QFileDialog, QFileDialog,
@@ -37,6 +45,14 @@ elif qt_config == 6:
QScrollArea, QScrollArea,
QVBoxLayout, QVBoxLayout,
QWidget, QWidget,
QGraphicsScene,
QGraphicsTextItem,
QGraphicsView,
QTableWidget,
QTableWidgetItem,
QHeaderView,
QLabel,
QSizePolicy,
) )


# ------------------------------------------------------------------------------------------------------------ # ------------------------------------------------------------------------------------------------------------
@@ -72,6 +88,7 @@ from carla_backend import (
PLUGIN_OPTION_SEND_ALL_SOUND_OFF, PLUGIN_OPTION_SEND_ALL_SOUND_OFF,
PLUGIN_OPTION_SEND_PROGRAM_CHANGES, PLUGIN_OPTION_SEND_PROGRAM_CHANGES,
PLUGIN_OPTION_SKIP_SENDING_NOTES, PLUGIN_OPTION_SKIP_SENDING_NOTES,
PARAMETER_NULL,
PARAMETER_DRYWET, PARAMETER_DRYWET,
PARAMETER_VOLUME, PARAMETER_VOLUME,
PARAMETER_BALANCE_LEFT, PARAMETER_BALANCE_LEFT,
@@ -84,6 +101,7 @@ from carla_backend import (
PARAMETER_USES_SCALEPOINTS, PARAMETER_USES_SCALEPOINTS,
PARAMETER_USES_CUSTOM_TEXT, PARAMETER_USES_CUSTOM_TEXT,
PARAMETER_CAN_BE_CV_CONTROLLED, PARAMETER_CAN_BE_CV_CONTROLLED,
parameterHintsText,
PARAMETER_INPUT, PARAMETER_OUTPUT, PARAMETER_INPUT, PARAMETER_OUTPUT,
CONTROL_INDEX_NONE, CONTROL_INDEX_NONE,
CONTROL_INDEX_MIDI_PITCHBEND, CONTROL_INDEX_MIDI_PITCHBEND,
@@ -94,6 +112,7 @@ from carla_backend import (
from carla_shared import ( from carla_shared import (
MIDI_CC_LIST, MAX_MIDI_CC_LIST_ITEM, MIDI_CC_LIST, MAX_MIDI_CC_LIST_ITEM,
countDecimalPoints, countDecimalPoints,
strLim,
fontMetricsHorizontalAdvance, fontMetricsHorizontalAdvance,
setUpSignals, setUpSignals,
gCarla gCarla
@@ -103,6 +122,8 @@ from carla_utils import getPluginTypeAsString


from widgets.collapsablewidget import CollapsibleBox from widgets.collapsablewidget import CollapsibleBox
from widgets.pixmapkeyboard import PixmapKeyboardHArea from widgets.pixmapkeyboard import PixmapKeyboardHArea
from widgets.paramspinbox import CustomInputDialog, ParamSpinBox
from widgets.scalabledial import ScalableDial


# ------------------------------------------------------------------------------------------------------------ # ------------------------------------------------------------------------------------------------------------
# Carla GUI defines # Carla GUI defines
@@ -119,6 +140,7 @@ class PluginParameter(QWidget):
mappedControlChanged = pyqtSignal(int, int) mappedControlChanged = pyqtSignal(int, int)
mappedRangeChanged = pyqtSignal(int, float, float) mappedRangeChanged = pyqtSignal(int, float, float)
midiChannelChanged = pyqtSignal(int, int) midiChannelChanged = pyqtSignal(int, int)
knobVisibilityChanged = pyqtSignal(int, int)
valueChanged = pyqtSignal(int, float) valueChanged = pyqtSignal(int, float)


def __init__(self, parent, host, pInfo, pluginId, tabIndex): def __init__(self, parent, host, pInfo, pluginId, tabIndex):
@@ -141,6 +163,7 @@ class PluginParameter(QWidget):
self.fParameterId = pInfo['index'] self.fParameterId = pInfo['index']
self.fPluginId = pluginId self.fPluginId = pluginId
self.fTabIndex = tabIndex self.fTabIndex = tabIndex
self.fKnobVisible = True


# ------------------------------------------------------------- # -------------------------------------------------------------
# Set-up GUI # Set-up GUI
@@ -157,6 +180,7 @@ class PluginParameter(QWidget):
self.ui.widget.setStep(pInfo['step']) self.ui.widget.setStep(pInfo['step'])
self.ui.widget.setStepSmall(pInfo['stepSmall']) self.ui.widget.setStepSmall(pInfo['stepSmall'])
self.ui.widget.setStepLarge(pInfo['stepLarge']) self.ui.widget.setStepLarge(pInfo['stepLarge'])
# NOTE: Issue #1983
self.ui.widget.setScalePoints(pInfo['scalePoints'], bool(pHints & PARAMETER_USES_SCALEPOINTS)) self.ui.widget.setScalePoints(pInfo['scalePoints'], bool(pHints & PARAMETER_USES_SCALEPOINTS))


if pInfo['comment']: if pInfo['comment']:
@@ -536,39 +560,21 @@ class PluginEdit(QDialog):
labelPluginFont.setWeight(75) labelPluginFont.setWeight(75)
self.ui.label_plugin.setFont(labelPluginFont) self.ui.label_plugin.setFont(labelPluginFont)


self.ui.dial_drywet.setCustomPaintMode(self.ui.dial_drywet.CUSTOM_PAINT_MODE_CARLA_WET)
self.ui.dial_drywet.setImage(3)
self.ui.dial_drywet.setLabel("Dry/Wet")
self.ui.dial_drywet.setMinimum(0.0)
self.ui.dial_drywet.setMaximum(1.0)
pluginHints = self.host.get_plugin_info(self.fPluginId)['hints']

self.ui.dial_drywet = ScalableDial(self.ui.dial_drywet, PARAMETER_DRYWET, 100, 1.0, 0.0, 1.0, "Dry/Wet", ScalableDial.CUSTOM_PAINT_MODE_CARLA_WET)
self.ui.dial_drywet.setValue(host.get_internal_parameter_value(pluginId, PARAMETER_DRYWET)) self.ui.dial_drywet.setValue(host.get_internal_parameter_value(pluginId, PARAMETER_DRYWET))


self.ui.dial_vol.setCustomPaintMode(self.ui.dial_vol.CUSTOM_PAINT_MODE_CARLA_VOL)
self.ui.dial_vol.setImage(3)
self.ui.dial_vol.setLabel("Volume")
self.ui.dial_vol.setMinimum(0.0)
self.ui.dial_vol.setMaximum(1.27)
self.ui.dial_vol = ScalableDial(self.ui.dial_vol, PARAMETER_VOLUME, 127, 1.0, 0.0, 1.27, "Volume", ScalableDial.CUSTOM_PAINT_MODE_CARLA_VOL)
self.ui.dial_vol.setValue(host.get_internal_parameter_value(pluginId, PARAMETER_VOLUME)) self.ui.dial_vol.setValue(host.get_internal_parameter_value(pluginId, PARAMETER_VOLUME))


self.ui.dial_b_left.setCustomPaintMode(self.ui.dial_b_left.CUSTOM_PAINT_MODE_CARLA_L)
self.ui.dial_b_left.setImage(4)
self.ui.dial_b_left.setLabel("L")
self.ui.dial_b_left.setMinimum(-1.0)
self.ui.dial_b_left.setMaximum(1.0)
self.ui.dial_b_left = ScalableDial(self.ui.dial_b_left, PARAMETER_BALANCE_LEFT, 100, -1.0, -1.0, 1.0, "L", ScalableDial.CUSTOM_PAINT_MODE_CARLA_L)
self.ui.dial_b_left.setValue(host.get_internal_parameter_value(pluginId, PARAMETER_BALANCE_LEFT)) self.ui.dial_b_left.setValue(host.get_internal_parameter_value(pluginId, PARAMETER_BALANCE_LEFT))


self.ui.dial_b_right.setCustomPaintMode(self.ui.dial_b_right.CUSTOM_PAINT_MODE_CARLA_R)
self.ui.dial_b_right.setImage(4)
self.ui.dial_b_right.setLabel("R")
self.ui.dial_b_right.setMinimum(-1.0)
self.ui.dial_b_right.setMaximum(1.0)
self.ui.dial_b_right = ScalableDial(self.ui.dial_b_right, PARAMETER_BALANCE_RIGHT, 100, 1.0, -1.0, 1.0, "R", ScalableDial.CUSTOM_PAINT_MODE_CARLA_R)
self.ui.dial_b_right.setValue(host.get_internal_parameter_value(pluginId, PARAMETER_BALANCE_RIGHT)) self.ui.dial_b_right.setValue(host.get_internal_parameter_value(pluginId, PARAMETER_BALANCE_RIGHT))


self.ui.dial_pan.setCustomPaintMode(self.ui.dial_b_right.CUSTOM_PAINT_MODE_CARLA_PAN)
self.ui.dial_pan.setImage(4)
self.ui.dial_pan.setLabel("Pan")
self.ui.dial_pan.setMinimum(-1.0)
self.ui.dial_pan.setMaximum(1.0)
self.ui.dial_pan = ScalableDial(self.ui.dial_pan, PARAMETER_PANNING, 100, 0, -1.0, 1.0, "Pan", ScalableDial.CUSTOM_PAINT_MODE_CARLA_PAN)
self.ui.dial_pan.setValue(host.get_internal_parameter_value(pluginId, PARAMETER_PANNING)) self.ui.dial_pan.setValue(host.get_internal_parameter_value(pluginId, PARAMETER_PANNING))


self.ui.sb_ctrl_channel.setValue(self.fControlChannel+1) self.ui.sb_ctrl_channel.setValue(self.fControlChannel+1)
@@ -582,10 +588,10 @@ class PluginEdit(QDialog):
self.ui.scrollArea.setVisible(False) self.ui.scrollArea.setVisible(False)


# todo # todo
self.ui.rb_balance.setEnabled(False)
self.ui.rb_balance.setVisible(False)
self.ui.rb_pan.setEnabled(False)
self.ui.rb_pan.setVisible(False)
# self.ui.rb_balance.setEnabled(False)
# self.ui.rb_balance.setVisible(False)
# self.ui.rb_pan.setEnabled(False)
# self.ui.rb_pan.setVisible(False)


flags = self.windowFlags() flags = self.windowFlags()
flags &= ~Qt.WindowContextHelpButtonHint flags &= ~Qt.WindowContextHelpButtonHint
@@ -901,7 +907,6 @@ class PluginEdit(QDialog):
break break


paramData = self.host.get_parameter_data(self.fPluginId, i) paramData = self.host.get_parameter_data(self.fPluginId, i)

if paramData['type'] not in (PARAMETER_INPUT, PARAMETER_OUTPUT): if paramData['type'] not in (PARAMETER_INPUT, PARAMETER_OUTPUT):
unusedParameters += 1 unusedParameters += 1
continue continue
@@ -974,6 +979,12 @@ class PluginEdit(QDialog):
self._createParameterWidgets(PARAMETER_INPUT, paramInputListFull, self.tr("Parameters")) self._createParameterWidgets(PARAMETER_INPUT, paramInputListFull, self.tr("Parameters"))
self._createParameterWidgets(PARAMETER_OUTPUT, paramOutputListFull, self.tr("Outputs")) self._createParameterWidgets(PARAMETER_OUTPUT, paramOutputListFull, self.tr("Outputs"))


# Create full parameter list table tab
self._createParameterXrayTab(self.tr("XRay"))

# Create experimental description tab WORKINPROGRESS NOTE discussions/1967, pull/1961
self._createDescriptionTab(self.tr("Description"))

# Restore tab state # Restore tab state
if tabIndex < self.ui.tabWidget.count(): if tabIndex < self.ui.tabWidget.count():
self.ui.tabWidget.setCurrentIndex(tabIndex) self.ui.tabWidget.setCurrentIndex(tabIndex)
@@ -1474,69 +1485,67 @@ class PluginEdit(QDialog):


@pyqtSlot() @pyqtSlot()
def slot_knobCustomMenu(self): def slot_knobCustomMenu(self):
sender = self.sender()
knobName = sender.objectName()

if knobName == "dial_drywet":
minimum = 0.0
maximum = 1.0
default = 1.0
label = "Dry/Wet"
elif knobName == "dial_vol":
minimum = 0.0
maximum = 1.27
default = 1.0
label = "Volume"
elif knobName == "dial_b_left":
minimum = -1.0
maximum = 1.0
default = -1.0
label = "Balance-Left"
elif knobName == "dial_b_right":
minimum = -1.0
maximum = 1.0
default = 1.0
label = "Balance-Right"
elif knobName == "dial_pan":
minimum = -1.0
maximum = 1.0
default = 0.0
label = "Panning"
# jpka: NOTE now Edit knobs are also know their constraints, so it's worth to set values as normal ones.
sender = self.sender()
index = sender.fIndex
minimum = sender.fMinimum
maximum = sender.fMaximum
current = sender.fRealValue
label = sender.fLabel
default = sender.fDefault
unit = sender.fUnit
step = stepSmall = 1.0

if index < PARAMETER_NULL:
percent = 100.0
else: else:
minimum = 0.0
maximum = 1.0
default = 0.5
label = "Unknown"
percent = 1

textReset = self.tr("Reset (" + strLim(default * percent) + unit + ")\tR, Middle click")
textMinim = self.tr("Set to Minimum (" + strLim(minimum * percent) + unit + ")\t0")
textMaxim = self.tr("Set to Maximum (" + strLim(maximum * percent) + unit + ")\tEnd")

if sender.fIsButton:
editHotKey = "E"
else:
editHotKey = "Enter, Double click"


menu = QMenu(self) menu = QMenu(self)
actReset = menu.addAction(self.tr("Reset (%i%%)" % (default*100)))
actReset = menu.addAction(textReset)
menu.addSeparator() menu.addSeparator()
actMinimum = menu.addAction(self.tr("Set to Minimum (%i%%)" % (minimum*100)))
actCenter = menu.addAction(self.tr("Set to Center"))
actMaximum = menu.addAction(self.tr("Set to Maximum (%i%%)" % (maximum*100)))
actMinimum = menu.addAction(textMinim)
actCenter = menu.addAction(self.tr("Set to Center\t5"))
actMaximum = menu.addAction(textMaxim)
menu.addSeparator() menu.addSeparator()
actSet = menu.addAction(self.tr("Set value..."))
actSet = menu.addAction(self.tr("Set value...\t" + editHotKey))


if label not in ("Balance-Left", "Balance-Right", "Panning"):
if index > PARAMETER_NULL or index not in (PARAMETER_BALANCE_LEFT, PARAMETER_BALANCE_RIGHT, PARAMETER_PANNING):
menu.removeAction(actCenter) menu.removeAction(actCenter)


actSelected = menu.exec_(QCursor.pos()) actSelected = menu.exec_(QCursor.pos())


if actSelected == actSet: if actSelected == actSet:
current = minimum + (maximum-minimum)*(float(sender.value())/10000)
value, ok = QInputDialog.getInt(self,
self.tr("Set value"),
label,
round(current*100.0),
round(minimum*100.0),
round(maximum*100.0),
1)
if ok:
value = float(value)/100.0
paramInfo = self.host.get_parameter_info(self.fPluginId, index)
paramRanges = self.host.get_parameter_ranges(self.fPluginId, index)
scalePoints = []


if not ok:
for i in range(paramInfo['scalePointCount']):
scalePoints.append(self.host.get_parameter_scalepoint_info(self.fPluginId, index, i))

if sender.fIsInteger:
step = max(1, int((maximum - minimum)/100))
stepSmall = max(1, int(step/10))
else:
step = paramRanges['step'] * percent
stepSmall = paramRanges['stepSmall'] * percent

dialog = CustomInputDialog(self, label, current * percent, minimum * percent, maximum * percent, step, stepSmall, scalePoints, "", "", unit)

if not dialog.exec_():
return return


value = dialog.returnValue() / percent

elif actSelected == actMinimum: elif actSelected == actMinimum:
value = minimum value = minimum
elif actSelected == actMaximum: elif actSelected == actMaximum:
@@ -1605,13 +1614,15 @@ class PluginEdit(QDialog):
scrollAreaLayout = QVBoxLayout(scrollAreaWidget) scrollAreaLayout = QVBoxLayout(scrollAreaWidget)
scrollAreaLayout.setSpacing(3) scrollAreaLayout.setSpacing(3)


expandBox = (len(paramList) < 50)

for paramInfo in paramList: for paramInfo in paramList:
groupName = paramInfo['groupName'] groupName = paramInfo['groupName']
if groupName: if groupName:
groupSymbol, groupName = groupName.split(":",1) groupSymbol, groupName = groupName.split(":",1)
groupLayout, groupWidget = groupWidgets.get(groupSymbol, (None, None)) groupLayout, groupWidget = groupWidgets.get(groupSymbol, (None, None))
if groupLayout is None: if groupLayout is None:
groupWidget = CollapsibleBox(groupName, scrollAreaWidget)
groupWidget = CollapsibleBox(groupName, scrollAreaWidget, expandBox)
groupLayout = groupWidget.getContentLayout() groupLayout = groupWidget.getContentLayout()
groupWidget.setPalette(palette2) groupWidget.setPalette(palette2)
scrollAreaLayout.addWidget(groupWidget) scrollAreaLayout.addWidget(groupWidget)
@@ -1678,6 +1689,140 @@ class PluginEdit(QDialog):


#------------------------------------------------------------------ #------------------------------------------------------------------


# NOTE To speed things up, displayed data is not realtime. Reopen project to expose last changes.
def _createParameterXrayTab(self, tabName):
# How simple would be fit a value into cell? Yet to be as fast as we can.
def strFit(value):
if isinstance(value, str):
return value
# For 'carla-control', but anyway scalePoints are not work. #1984
elif isinstance(value, list):
return str(value) # It's []
elif abs(value) >= 1E8:
return '{:.3e}'.format(value)
elif value == int(value): # Zero falls here
return str(int(value))
else:
return strLim(value)

def strLineWrap(string, cut):
result = ''
while len(string) > cut: # FIXME Optimize me!
result += string[:cut] + '\n'
string = string[cut:]
result += string
return result

def addCell(section, name, string, toolTip = ''):
if x == table.columnCount():
table.insertColumn(x)
# jpka: FIXME Here we need vertical text. But impossible, no working examples.
# Only untested https://stackoverflow.com/questions/52162125/
nameWrapped = section + '\n' + strLineWrap(name, 6)
table.setHorizontalHeaderItem(x, QTableWidgetItem(nameWrapped))
table.horizontalHeader().setSectionResizeMode(x, QHeaderView.ResizeToContents)

if y == table.rowCount():
table.insertRow(y)
table.verticalHeader().setSectionResizeMode(y, QHeaderView.ResizeToContents)

item = QTableWidgetItem(string)
if toolTip:
item.setToolTip(toolTip)
table.setItem(y, x, item)
return

table = QTableWidget(self)
table.setObjectName("table")
table.setRowCount(1)
# table.setToolTipDuration(2000)

parameterCount = self.host.get_parameter_count(self.fPluginId)
if parameterCount <= 0:
return

y = 0
for i in range(parameterCount):
x = 0
param = self.host.get_parameter_data(self.fPluginId, i)
for name in param:
value = param[name]
if (name == 'type') and (value in (1, 2,)):
addCell('Data', name, str(value) + (' in',' out')[value - 1])
elif (name == 'hints'):
# toolTip = '<body>' + bin(value)[2:]
hints = ''
for bit in range(len(parameterHintsText)):
if (value & int(2**(bit-1))):
hint = parameterHintsText[bit-1]
# toolTip += '<br>' + hint
hints += ', ' + hint
addCell('Data', name, str(value)) # , toolTip + '</body>')
x += 1
addCell('Hints', '', hints[2:])
else:
addCell('Data', name, strFit(value))
x += 1

param = self.host.get_parameter_ranges(self.fPluginId, i)
for name in param:
addCell('Ranges', name, strFit(param[name]))
x += 1

param = self.host.get_parameter_info(self.fPluginId, i)
for name in param:
addCell('Info', name, strFit(param[name]))
x += 1

strScalePoints = ''
for j in range(param['scalePointCount']):
scalePointInfo = self.host.get_parameter_scalepoint_info(self.fPluginId, i, j)
strScalePoints += (strFit(scalePointInfo['value']) + ':' + scalePointInfo['label'] + ',')

if strScalePoints:
addCell('Scalepoint_info', 'Scale Points', strLineWrap(strScalePoints[:len(strScalePoints)-1], 80))
x += 1

y += 1

self.ui.tabWidget.addTab(table, tabName)
# self.ui.tabWidget.setToolTipDuration(2000)

#------------------------------------------------------------------

def _createDescriptionTab(self, tabPageName):
# jpka: To be filled from 'rdfs:comment'
strDescr = "To be filled from rdfs:comment"

realPluginName = self.host.get_real_plugin_name(self.fPluginId)
labelURI = self.fPluginInfo['label']

strLoadState = ""
programCount = self.host.get_program_count(self.fPluginId)
if programCount > 0:
strLoadState = '<div style="letter-spacing:1px"><br>'\
'<b>Note: </b>This plugin collected some presets for you.<br>'\
'Use <i>Edit</i> tab, then <i>Load State</i> button.</div>'

scene = QGraphicsScene(self)
text = QGraphicsTextItem("",None)
text.setTextInteractionFlags(Qt.TextSelectableByMouse)
text.setTextWidth(600);
# text.setFont(QFont("Arial, 16")) # NOTE: All Qt sizes are in Pt; real px~=4/3Pt.
text.setHtml('<body>\
<h1>' + realPluginName + '</h1><br>'\
'<a href=' + labelURI + '>' + labelURI + '</a><br><br>'\
'<div style="line-height:1.5;">' + strDescr + '</div>' +\
strLoadState +\
'<body>');

scene.addItem(text)
view = QGraphicsView(scene, self)

self.ui.tabWidget.addTab(view, tabPageName)

#------------------------------------------------------------------

def testTimer(self): def testTimer(self):
self.fIdleTimerId = self.startTimer(50) self.fIdleTimerId = self.startTimer(50)




+ 5
- 3
source/frontend/widgets/collapsablewidget.py View File

@@ -33,7 +33,7 @@ class QToolButtonWithMouseTracking(QToolButton):
QToolButton.leaveEvent(self, event) QToolButton.leaveEvent(self, event)


class CollapsibleBox(QFrame): class CollapsibleBox(QFrame):
def __init__(self, title, parent):
def __init__(self, title, parent, startsExpanded = True):
QFrame.__init__(self, parent) QFrame.__init__(self, parent)


self.setFrameShape(QFrame.StyledPanel) self.setFrameShape(QFrame.StyledPanel)
@@ -42,10 +42,10 @@ class CollapsibleBox(QFrame):
self.toggle_button = QToolButtonWithMouseTracking(self) self.toggle_button = QToolButtonWithMouseTracking(self)
self.toggle_button.setText(title) self.toggle_button.setText(title)
self.toggle_button.setCheckable(True) self.toggle_button.setCheckable(True)
self.toggle_button.setChecked(True)
self.toggle_button.setChecked(startsExpanded)
self.toggle_button.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) self.toggle_button.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
self.toggle_button.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) self.toggle_button.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
self.toggle_button.setArrowType(Qt.DownArrow)
# self.toggle_button.setArrowType(Qt.DownArrow) # Not deleted, just moved
self.toggle_button.toggled.connect(self.toolButtonPressed) self.toggle_button.toggled.connect(self.toolButtonPressed)


self.content_area = QWidget(self) self.content_area = QWidget(self)
@@ -59,6 +59,8 @@ class CollapsibleBox(QFrame):
lay.addWidget(self.toggle_button) lay.addWidget(self.toggle_button)
lay.addWidget(self.content_area) lay.addWidget(self.content_area)


self.toolButtonPressed(startsExpanded) # Set initial state

@pyqtSlot(bool) @pyqtSlot(bool)
def toolButtonPressed(self, toggled): def toolButtonPressed(self, toggled):
self.content_area.setVisible(toggled) self.content_area.setVisible(toggled)


+ 406
- 96
source/frontend/widgets/commondial.py View File

@@ -1,22 +1,26 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# SPDX-FileCopyrightText: 2011-2024 Filipe Coelho <falktx@falktx.com>
# SPDX-FileCopyrightText: 2011-2025 Filipe Coelho <falktx@falktx.com>
# SPDX-License-Identifier: GPL-2.0-or-later # SPDX-License-Identifier: GPL-2.0-or-later


# --------------------------------------------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------------------------------------------
# Imports (Global) # Imports (Global)


from math import isnan
from math import isnan, log10


from qt_compat import qt_config from qt_compat import qt_config


if qt_config == 5: if qt_config == 5:
from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QPointF, QRectF
from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QPoint, QPointF, QRectF, QEvent, QTimer
from PyQt5.QtGui import QColor, QFont, QLinearGradient, QPainter from PyQt5.QtGui import QColor, QFont, QLinearGradient, QPainter
from PyQt5.QtWidgets import QDial
from PyQt5.QtWidgets import QWidget, QToolTip, QInputDialog
elif qt_config == 6: elif qt_config == 6:
from PyQt6.QtCore import pyqtSignal, pyqtSlot, Qt, QPointF, QRectF
from PyQt6.QtCore import pyqtSignal, pyqtSlot, Qt, QPoint, QPointF, QRectF, QEvent, QTimer
from PyQt6.QtGui import QColor, QFont, QLinearGradient, QPainter from PyQt6.QtGui import QColor, QFont, QLinearGradient, QPainter
from PyQt6.QtWidgets import QDial
from PyQt6.QtWidgets import QWidget, QToolTip, QInputDialog

from carla_shared import strLim
from widgets.paramspinbox import CustomInputDialog
from carla_backend import PARAMETER_NULL, PARAMETER_DRYWET, PARAMETER_VOLUME, PARAMETER_BALANCE_LEFT, PARAMETER_BALANCE_RIGHT, PARAMETER_PANNING


# --------------------------------------------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------------------------------------------
# Widget Class # Widget Class
@@ -25,7 +29,7 @@ elif qt_config == 6:
#def updateSizes(self): #def updateSizes(self):
#def paintDial(self, painter): #def paintDial(self, painter):


class CommonDial(QDial):
class CommonDial(QWidget):
# enum CustomPaintMode # enum CustomPaintMode
CUSTOM_PAINT_MODE_NULL = 0 # default (NOTE: only this mode has label gradient) CUSTOM_PAINT_MODE_NULL = 0 # default (NOTE: only this mode has label gradient)
CUSTOM_PAINT_MODE_CARLA_WET = 1 # color blue-green gradient (reserved #3) CUSTOM_PAINT_MODE_CARLA_WET = 1 # color blue-green gradient (reserved #3)
@@ -33,9 +37,11 @@ class CommonDial(QDial):
CUSTOM_PAINT_MODE_CARLA_L = 3 # color yellow (reserved #4) CUSTOM_PAINT_MODE_CARLA_L = 3 # color yellow (reserved #4)
CUSTOM_PAINT_MODE_CARLA_R = 4 # color yellow (reserved #4) CUSTOM_PAINT_MODE_CARLA_R = 4 # color yellow (reserved #4)
CUSTOM_PAINT_MODE_CARLA_PAN = 5 # color yellow (reserved #3) CUSTOM_PAINT_MODE_CARLA_PAN = 5 # color yellow (reserved #3)
CUSTOM_PAINT_MODE_COLOR = 6 # color, selectable (reserved #3)
CUSTOM_PAINT_MODE_ZITA = 7 # custom zita knob (reserved #6)
CUSTOM_PAINT_MODE_CARLA_FORTH = 6 # Experimental
CUSTOM_PAINT_MODE_COLOR = 7 # May be deprecated (unless zynfx internal mode)
CUSTOM_PAINT_MODE_NO_GRADIENT = 8 # skip label gradient CUSTOM_PAINT_MODE_NO_GRADIENT = 8 # skip label gradient
CUSTOM_PAINT_MODE_CARLA_WET_MINI = 9 # for compacted slot
CUSTOM_PAINT_MODE_CARLA_VOL_MINI = 10 # for compacted slot


# enum Orientation # enum Orientation
HORIZONTAL = 0 HORIZONTAL = 0
@@ -51,16 +57,37 @@ class CommonDial(QDial):
dragStateChanged = pyqtSignal(bool) dragStateChanged = pyqtSignal(bool)
realValueChanged = pyqtSignal(float) realValueChanged = pyqtSignal(float)


def __init__(self, parent, index):
QDial.__init__(self, parent)
def __init__(self, parent, index, precision, default, minimum, maximum, label, paintMode, colorHint, unit, skinStyle, whiteLabels, tweaks, isInteger, isButton, isOutput, isVuOutput, isVisible):
QWidget.__init__(self, parent)

self.fIndex = index
self.fPrecision = precision
self.fDefault = default
self.fMinimum = minimum
self.fMaximum = maximum
self.fCustomPaintMode = paintMode
self.fColorHint = colorHint
self.fUnit = unit
self.fSkinStyle = skinStyle
self.fWhiteLabels = whiteLabels
self.fTweaks = tweaks
self.fIsInteger = isInteger
self.fIsButton = isButton
self.fIsOutput = isOutput
self.fIsVuOutput = isVuOutput
self.fIsVisible = isVisible


self.fDialMode = self.MODE_LINEAR self.fDialMode = self.MODE_LINEAR


self.fMinimum = 0.0
self.fMaximum = 1.0
self.fLabel = label
self.fLastLabel = ""
self.fRealValue = 0.0 self.fRealValue = 0.0
self.fPrecision = 10000
self.fIsInteger = False
self.fLastValue = self.fDefault

self.fScalePoints = []
self.fNumScalePoints = 0
self.fScalePointsPrefix = ""
self.fScalePointsSuffix = ""


self.fIsHovered = False self.fIsHovered = False
self.fIsPressed = False self.fIsPressed = False
@@ -69,9 +96,6 @@ class CommonDial(QDial):
self.fLastDragPos = None self.fLastDragPos = None
self.fLastDragValue = 0.0 self.fLastDragValue = 0.0


self.fIndex = index

self.fLabel = ""
self.fLabelPos = QPointF(0.0, 0.0) self.fLabelPos = QPointF(0.0, 0.0)
self.fLabelFont = QFont(self.font()) self.fLabelFont = QFont(self.font())
self.fLabelFont.setPixelSize(8) self.fLabelFont.setPixelSize(8)
@@ -97,75 +121,79 @@ class CommonDial(QDial):


self.fLabelGradientRect = QRectF(0.0, 0.0, 0.0, 0.0) self.fLabelGradientRect = QRectF(0.0, 0.0, 0.0, 0.0)


self.fCustomPaintMode = self.CUSTOM_PAINT_MODE_NULL
self.fCustomPaintColor = QColor(0xff, 0xff, 0xff) self.fCustomPaintColor = QColor(0xff, 0xff, 0xff)


# Fake internal value, custom precision
QDial.setMinimum(self, 0)
QDial.setMaximum(self, self.fPrecision)
QDial.setValue(self, 0)
self.addContrast = int(bool(self.getTweak('HighContrast', 0)))
self.colorFollow = bool(self.getTweak('ColorFollow', 0))
self.knobPusheable = bool(self.getTweak('WetVolPush', 0))
self.displayTooltip = bool(self.getTweak('Tooltips', 1))


self.valueChanged.connect(self.slot_valueChanged)
# We have two group of knobs, non-repaintable (like in Edit dialog) and normal.
# For non-repaintable, we init sizes/color once here;
# for normals, it should be (re)inited separately: we do not init it here
# to save CPU, some parameters are not known yet, repaint need anyway.
if self.fColorHint == -1:
self.updateSizes()

self.update()

# self.valueChanged.connect(self.slot_valueChanged) # FIXME


def forceWhiteLabelGradientText(self): def forceWhiteLabelGradientText(self):
self.fLabelGradientColor1 = QColor(0, 0, 0, 255) self.fLabelGradientColor1 = QColor(0, 0, 0, 255)
self.fLabelGradientColor2 = QColor(0, 0, 0, 0) self.fLabelGradientColor2 = QColor(0, 0, 0, 0)
self.fLabelGradientColorT = [Qt.white, Qt.darkGray] self.fLabelGradientColorT = [Qt.white, Qt.darkGray]


def setLabelColor(self, enabled, disabled):
self.fLabelGradientColor1 = QColor(0, 0, 0, 255)
self.fLabelGradientColor2 = QColor(0, 0, 0, 0)
self.fLabelGradientColorT = [enabled, disabled]
# def setLabelColor(self, enabled, disabled):
# self.fLabelGradientColor1 = QColor(0, 0, 0, 255)
# self.fLabelGradientColor2 = QColor(0, 0, 0, 0)
# self.fLabelGradientColorT = [enabled, disabled]


def getIndex(self): def getIndex(self):
return self.fIndex return self.fIndex


def setIndex(self, index):
self.fIndex = index

def setPrecision(self, value, isInteger):
self.fPrecision = value
self.fIsInteger = isInteger
QDial.setMaximum(self, int(value))

def setMinimum(self, value):
self.fMinimum = value

def setMaximum(self, value):
self.fMaximum = value

def rvalue(self): def rvalue(self):
return self.fRealValue return self.fRealValue


def pushLabel(self, label):
if self.fLastLabel == "":
self.fLastLabel = self.fLabel
self.fLabel = label
self.updateSizes()
self.update()

def popLabel(self):
if not (self.fLastLabel == ""):
self.fLabel = self.fLastLabel
self.fLastLabel = ""
self.updateSizes()
self.update()

def setScalePPS(self, scalePoints, prefix, suffix):
self.fScalePoints = scalePoints
self.fNumScalePoints = len(self.fScalePoints)
self.fScalePointsPrefix = prefix
self.fScalePointsSuffix = suffix

def setValue(self, value, emitSignal=False): def setValue(self, value, emitSignal=False):
if self.fRealValue == value or isnan(value): if self.fRealValue == value or isnan(value):
return return


if value <= self.fMinimum:
qtValue = 0
if (not self.fIsOutput) and value <= self.fMinimum:
self.fRealValue = self.fMinimum self.fRealValue = self.fMinimum


elif value >= self.fMaximum:
qtValue = int(self.fPrecision)
elif (not self.fIsOutput) and value >= self.fMaximum:
self.fRealValue = self.fMaximum self.fRealValue = self.fMaximum


elif self.fIsInteger or (abs(value - int(value)) < 1e-8): # tiny "notch"
self.fRealValue = round(value)

else: else:
qtValue = round(float(value - self.fMinimum) / float(self.fMaximum - self.fMinimum) * self.fPrecision)
self.fRealValue = value self.fRealValue = value


# Block change signal, we'll handle it ourselves
self.blockSignals(True)
QDial.setValue(self, qtValue)
self.blockSignals(False)

if emitSignal: if emitSignal:
self.realValueChanged.emit(self.fRealValue) self.realValueChanged.emit(self.fRealValue)


def setCustomPaintMode(self, paintMode):
if self.fCustomPaintMode == paintMode:
return

self.fCustomPaintMode = paintMode
self.update() self.update()


def setCustomPaintColor(self, color): def setCustomPaintColor(self, color):
@@ -173,76 +201,262 @@ class CommonDial(QDial):
return return


self.fCustomPaintColor = color self.fCustomPaintColor = color
self.updateSizes()
self.update() self.update()


def setLabel(self, label):
if self.fLabel == label:
return
def getTweak(self, tweakName, default):
return self.fTweaks.get(self.fSkinStyle + tweakName, self.fTweaks.get(tweakName, default))


self.fLabel = label
self.updateSizes()
self.update()
def getIsVisible(self):
# print (self.fIsVisible)
return self.fIsVisible


@pyqtSlot(int) @pyqtSlot(int)
def slot_valueChanged(self, value): def slot_valueChanged(self, value):
self.fRealValue = float(value)/self.fPrecision * (self.fMaximum - self.fMinimum) + self.fMinimum self.fRealValue = float(value)/self.fPrecision * (self.fMaximum - self.fMinimum) + self.fMinimum
self.realValueChanged.emit(self.fRealValue) self.realValueChanged.emit(self.fRealValue)


# jpka: TODO should be replaced by common dialog, but
# PluginEdit.slot_knobCustomMenu(...) - not found, import not work.
# So this is copy w/o access to 'step's.
def knobCustomInputDialog(self):
if self.fIndex < PARAMETER_NULL:
percent = 100.0
else:
percent = 1

if self.fIsInteger:
step = max(1, int((self.fMaximum - self.fMinimum)/100))
stepSmall = max(1, int(step/10))
else:
step = 10 ** (round(log10((self.fMaximum - self.fMinimum) * percent))-2)
stepSmall = step / 100

dialog = CustomInputDialog(self, self.fLabel, self.fRealValue * percent, self.fMinimum * percent, self.fMaximum * percent, step, stepSmall, self.fScalePoints, "", "", self.fUnit)
if not dialog.exec_():
return

self.setValue(dialog.returnValue() / percent, True)


def enterEvent(self, event): def enterEvent(self, event):
self.setFocus()
self.fIsHovered = True self.fIsHovered = True
if self.fHoverStep == self.HOVER_MIN: if self.fHoverStep == self.HOVER_MIN:
self.fHoverStep = self.HOVER_MIN + 1 self.fHoverStep = self.HOVER_MIN + 1
QDial.enterEvent(self, event)
self.update()



def leaveEvent(self, event): def leaveEvent(self, event):
self.fIsHovered = False self.fIsHovered = False
if self.fHoverStep == self.HOVER_MAX: if self.fHoverStep == self.HOVER_MAX:
self.fHoverStep = self.HOVER_MAX - 1 self.fHoverStep = self.HOVER_MAX - 1
QDial.leaveEvent(self, event)
self.update()


def nextScalePoint(self):
for i in range(self.fNumScalePoints):
value = self.fScalePoints[i]['value']
if value > self.fRealValue:
self.setValue(value, True)
return
self.setValue(self.fScalePoints[0]['value'], True)



def mousePressEvent(self, event): def mousePressEvent(self, event):
if self.fDialMode == self.MODE_DEFAULT:
QDial.mousePressEvent(self, event)
if self.fDialMode == self.MODE_DEFAULT or self.fIsOutput:
return return


if event.button() == Qt.LeftButton: if event.button() == Qt.LeftButton:
self.fIsPressed = True
self.fLastDragPos = event.pos()
self.fLastDragValue = self.fRealValue
self.dragStateChanged.emit(True)
# if self.fNumScalePoints:
# self.nextScalePoint()
#
if self.fIsButton:
value = int(self.fRealValue) + 1;
if (value > self.fMaximum):
value = 0
self.setValue(value, True)
else:
self.fIsPressed = True
self.fLastDragPos = event.pos()
self.fLastDragValue = self.fRealValue
self.dragStateChanged.emit(True)

elif event.button() == Qt.MiddleButton:
if self.fIsOutput:
return
self.setValue(self.fDefault, True)


def mouseDoubleClickEvent(self, event):
if self.knobPusheable and self.fIndex in (PARAMETER_DRYWET, PARAMETER_VOLUME, PARAMETER_PANNING): # -3, -4, -7
return # Mutex with special Single Click

if event.button() == Qt.LeftButton:
if self.fIsButton:
value = int(self.fRealValue) + 1;
if (value > self.fMaximum):
value = 0
self.setValue(value, True)
else:
if self.fIsOutput:
return

self.knobCustomInputDialog()



def mouseMoveEvent(self, event): def mouseMoveEvent(self, event):
if self.fDialMode == self.MODE_DEFAULT:
QDial.mouseMoveEvent(self, event)
if self.fDialMode == self.MODE_DEFAULT or self.fIsOutput:
return return


if not self.fIsPressed: if not self.fIsPressed:
return return


diff = (self.fMaximum - self.fMinimum) / 4.0
pos = event.pos()
dx = diff * float(pos.x() - self.fLastDragPos.x()) / self.width()
dy = diff * float(pos.y() - self.fLastDragPos.y()) / self.height()
value = self.fLastDragValue + dx - dy
pos = event.pos()
delta = (float(pos.x() - self.fLastDragPos.x()) - float(pos.y() - self.fLastDragPos.y())) / 10


if value < self.fMinimum:
value = self.fMinimum
elif value > self.fMaximum:
value = self.fMaximum
elif self.fIsInteger:
value = float(round(value))
mod = event.modifiers()
self.applyDelta(mod, delta, True)


self.setValue(value, True)


def mouseReleaseEvent(self, event): def mouseReleaseEvent(self, event):
if self.fDialMode == self.MODE_DEFAULT:
QDial.mouseReleaseEvent(self, event)
if self.fDialMode == self.MODE_DEFAULT or self.fIsOutput:
return return


if self.fIsPressed: if self.fIsPressed:
self.fIsPressed = False self.fIsPressed = False
self.dragStateChanged.emit(False) self.dragStateChanged.emit(False)


if event.button() == Qt.LeftButton:
if event.pos() == self.fLastDragPos:
if self.fNumScalePoints:
self.nextScalePoint()
else:
self.knobPush()

# NOTE: fLastLabel state managed @ scalabledial
def knobPush(self):
if self.knobPusheable and self.fIndex in (PARAMETER_DRYWET, PARAMETER_VOLUME, PARAMETER_PANNING): # -3, -4, -7

if self.fLastLabel == "": # push value
self.fLastValue = self.fRealValue
self.setValue(0, True) # Thru or Mute
else: # pop value
self.setValue(self.fLastValue, True)


def applyDelta(self, mod, delta, anchor = False):
if self.fIsOutput:
return

if self.fIsButton:
self.setValue(self.fRealValue + delta, True)
return

if self.fIsInteger: # 4 to 50 ticks per revolution
if (mod & Qt.ShiftModifier):
delta = delta * 5
elif (mod & Qt.ControlModifier):
delta = delta / min(int((self.fMaximum-self.fMinimum)/self.fPrecision), 5)
else: # Floats are 250 to 500 ticks per revolution
# jpka: 1. Should i use these steps?
# 2. And what do i do when i TODO add MODE_LOG along with MODE_LINEAR?
# 3. And they're too small for large ints like in TAP Reverb, and strange for scalepoints.
# paramRanges = self.host.get_parameter_ranges(self.fPluginId, i)
# paramRanges['step'], paramRanges['stepSmall'], paramRanges['stepLarge']
if (mod & Qt.ControlModifier) and (mod & Qt.ShiftModifier):
delta = delta * 2/5
elif (mod & Qt.ControlModifier):
delta = delta * 2
elif (mod & Qt.ShiftModifier):
delta = delta * 50
else:
delta = delta * 10

difference = float(self.fMaximum-self.fMinimum) * float(delta) / float(self.fPrecision)

if anchor:
self.setValue((self.fLastDragValue + difference), True)
else:
self.setValue((self.fRealValue + difference), True)

return


def wheelEvent(self, event):
if self.fIsOutput:
return

direction = event.angleDelta().y()
if direction < 0:
delta = -1.0
elif direction > 0:
delta = 1.0
else:
return

mod = event.modifiers()
self.applyDelta(mod, delta)
return


def keyPressEvent(self, event):
if self.fIsOutput:
return

key = event.key()
mod = event.modifiers()
modsNone = not ((mod & Qt.ShiftModifier) | (mod & Qt.ControlModifier) | (mod & Qt.AltModifier))

if modsNone:
match key:
case Qt.Key_Space | Qt.Key_Enter | Qt.Key_Return :
if self.fIsButton:
value = int(self.fRealValue) + 1
if (value > self.fMaximum):
value = 0
self.setValue(value, True)

elif not key == Qt.Key_Space:
self.knobCustomInputDialog()
else:
if self.fNumScalePoints:
self.nextScalePoint()
else:
self.knobPush()

case Qt.Key_E:
self.knobCustomInputDialog()

case key if Qt.Key_0 <= key <= Qt.Key_9:
if self.fIsInteger and (self.fMinimum == 0) and (self.fMaximum <= 10):
self.setValue(key-Qt.Key_0, True)

else:
self.setValue(self.fMinimum + float(self.fMaximum-self.fMinimum)/10.0*(key-Qt.Key_0), True)

case Qt.Key_Home: # NOTE: interferes with Canvas control hotkey
self.setValue(self.fMinimum, True)

case Qt.Key_End:
self.setValue(self.fMaximum, True)

case Qt.Key_D:
self.setValue(self.fDefault, True)

case Qt.Key_R:
self.setValue(self.fDefault, True)

match key:
case Qt.Key_PageDown:
self.applyDelta(mod, -1)

case Qt.Key_PageUp:
self.applyDelta(mod, 1)

return


def paintEvent(self, event): def paintEvent(self, event):
painter = QPainter(self) painter = QPainter(self)
event.accept() event.accept()
@@ -250,22 +464,118 @@ class CommonDial(QDial):
painter.save() painter.save()
painter.setRenderHint(QPainter.Antialiasing, True) painter.setRenderHint(QPainter.Antialiasing, True)


enabled = int(bool(self.isEnabled()))
if enabled:
self.setContextMenuPolicy(Qt.CustomContextMenu)
else:
self.setContextMenuPolicy(Qt.NoContextMenu)

if self.fLabel: if self.fLabel:
if self.fCustomPaintMode == self.CUSTOM_PAINT_MODE_NULL:
painter.setPen(self.fLabelGradientColor2)
painter.setBrush(self.fLabelGradient)
painter.drawRect(self.fLabelGradientRect)
# if self.fCustomPaintMode == self.CUSTOM_PAINT_MODE_NULL:
# painter.setPen(self.fLabelGradientColor2)
# painter.setBrush(self.fLabelGradient)
# # painter.drawRect(self.fLabelGradientRect) FIXME restore gradients.

luma = int(bool(self.fWhiteLabels)) - 0.5
if enabled:
L = (luma * (1.6 + self.addContrast * 0.4)) / 2 + 0.5
else:
L = (luma * (0.2 + self.addContrast * 0.2)) / 2 + 0.5


painter.setFont(self.fLabelFont) painter.setFont(self.fLabelFont)
painter.setPen(self.fLabelGradientColorT[0 if self.isEnabled() else 1])
# painter.setPen(self.fLabelGradientColorT[0 if self.fIsEnabled() else 1])
painter.setPen(QColor.fromHslF(0, 0, L, 1))
painter.drawText(self.fLabelPos, self.fLabel) painter.drawText(self.fLabelPos, self.fLabel)


self.paintDial(painter)
X = self.fWidth / 2
Y = self.fHeight / 2

S = enabled * 0.9 # saturation

E = enabled * self.fHoverStep / 40 # enlight
L = 0.6 + E
if self.addContrast:
L = min(L + 0.3, 1) # luma

normValue = float(self.fRealValue - self.fMinimum) / float(self.fMaximum - self.fMinimum)
# Work In Progress FIXME
H=0
if self.fIsOutput:
if self.fIsButton:
# self.paintLed (painter, X, Y, H, S, L, E, normValue)
self.paintDisplay(painter, X, Y, H, S, L, E, normValue, enabled)
else:
self.paintDisplay(painter, X, Y, H, S, L, E, normValue, enabled)
else:
if self.fIsButton:
self.paintButton (painter, X, Y, H, S, L, E, normValue, enabled)
else:
self.paintDial (painter, X, Y, H, S, L, E, normValue, enabled)

# Display tooltip, above the knob (OS-independent, unlike of mouse tooltip).
# Note, update/redraw Qt's tooltip eats much more CPU than expected,
# so we have tweak for turn it off. See also #1934.
if self.fHoverStep == self.HOVER_MAX and self.displayTooltip:
# First, we need to find exact or nearest match (index from value).
# It is also tests if we have scale points at all.
num = -1
for i in range(self.fNumScalePoints):
scaleValue = self.fScalePoints[i]['value']
if i == 0:
finalValue = scaleValue
num = 0
else:
srange2 = abs(self.fRealValue - finalValue)
srange1 = abs(self.fRealValue - scaleValue)
if srange2 > srange1:
finalValue = scaleValue
num = i
if (srange1 == 0): # Exact match, save some CPU.
break

tip = ""
if (num >= 0): # Scalepoints are used
tip = str(self.fScalePoints[num]['label'])
if not self.fIsButton:
tip = self.fScalePointsPrefix + \
strLim(self.fScalePoints[num]['value']) + \
self.fScalePointsSuffix + ": " + tip
# ? We most probably not need tooltip for button, if it is not scalepoint.
# elif not self.fIsButton:
else:
if self.fRealValue == 0 and self.fIndex == PARAMETER_DRYWET: #-3,-4,-7,-9
tip = "THRU"
elif self.fRealValue == 0 and self.fIndex == PARAMETER_VOLUME:
tip = "MUTE"
elif self.fRealValue == 0 and self.fIndex == PARAMETER_PANNING:
tip = "Center"
else:
if self.fIndex < PARAMETER_NULL:
percent = 100.0
else:
percent = 1

tip = (strLim(self.fRealValue * percent) + " " + self.fUnit).strip()
if self.fIsOutput:
tip = tip + " [" + strLim(self.fMinimum * percent) + "..." + \
strLim(self.fMaximum * percent) + "]"

# Wrong vert. position for Calf:
# QToolTip.showText(self.mapToGlobal(QPoint(0, 0-self.geometry().height())), tip)
# FIXME Still wrong vert. position for QT_SCALE_FACTOR=2.
QToolTip.showText(self.mapToGlobal(QPoint(0, 0-45)), tip)
else:
QToolTip.hideText()

if enabled:
if self.HOVER_MIN < self.fHoverStep < self.HOVER_MAX:
self.fHoverStep += 1 if self.fIsHovered else -1
QTimer.singleShot(20, self.update)


painter.restore() painter.restore()


def resizeEvent(self, event):
QDial.resizeEvent(self, event)
self.updateSizes()
# def resizeEvent(self, event):
# QWidget.resizeEvent(self, event)
# self.updateSizes()


# --------------------------------------------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------------------------------------------

+ 37
- 8
source/frontend/widgets/digitalpeakmeter.py View File

@@ -35,6 +35,7 @@ class DigitalPeakMeter(QWidget):
STYLE_OPENAV = 2 STYLE_OPENAV = 2
STYLE_RNCBC = 3 STYLE_RNCBC = 3
STYLE_CALF = 4 STYLE_CALF = 4
STYLE_TUBE = 5


# ----------------------------------------------------------------------------------------------------------------- # -----------------------------------------------------------------------------------------------------------------


@@ -153,7 +154,7 @@ class DigitalPeakMeter(QWidget):
if self.fMeterStyle == style: if self.fMeterStyle == style:
return return


if style not in (self.STYLE_DEFAULT, self.STYLE_OPENAV, self.STYLE_RNCBC, self.STYLE_CALF):
if style not in (self.STYLE_DEFAULT, self.STYLE_OPENAV, self.STYLE_RNCBC, self.STYLE_CALF, self.STYLE_TUBE):
qCritical(f"DigitalPeakMeter::setMeterStyle({style}) - invalid style") qCritical(f"DigitalPeakMeter::setMeterStyle({style}) - invalid style")
return return


@@ -163,7 +164,7 @@ class DigitalPeakMeter(QWidget):
self.fMeterBackground = QColor("#1A1A1A") self.fMeterBackground = QColor("#1A1A1A")
elif style == self.STYLE_RNCBC: elif style == self.STYLE_RNCBC:
self.fMeterBackground = QColor("#070707") self.fMeterBackground = QColor("#070707")
elif style == self.STYLE_CALF:
elif style in (self.STYLE_CALF, self.STYLE_TUBE):
self.fMeterBackground = QColor("#000") self.fMeterBackground = QColor("#000")


if style == self.STYLE_CALF: if style == self.STYLE_CALF:
@@ -215,23 +216,36 @@ class DigitalPeakMeter(QWidget):


i = meter - 1 i = meter - 1


if level < 0.001:
level = 0.0
elif level > 0.999:
level = 1.0

if self.fSmoothMultiplier > 0 and not forced: if self.fSmoothMultiplier > 0 and not forced:
level = ( level = (
(self.fLastChannelData[i] * float(self.fSmoothMultiplier) + level) (self.fLastChannelData[i] * float(self.fSmoothMultiplier) + level)
/ float(self.fSmoothMultiplier + 1) / float(self.fSmoothMultiplier + 1)
) )


if level < 0.001:
level = 0.0
elif level > 0.999:
level = 1.0
self.fLastChannelData[i] = level

# Discretize scale: for 10 points, first will lit at 5%,
# then 15%, and last at 95% of normalized value.
# We also win some CPU when not redraw at small changes.
if (self.fMeterStyle == self.STYLE_TUBE) and (level > 0.0) and (level < 1.0):
points = 20

# Transform to Sq Root domain: our meters have Sqrt dynamic compression;
# Discretize:
level = int(sqrt(level) * points + 0.5) / points

# Transform back from Square Root domain:
level = level * level


if self.fChannelData[i] != level: if self.fChannelData[i] != level:
self.fChannelData[i] = level self.fChannelData[i] = level
self.update() self.update()


self.fLastChannelData[i] = level

# ----------------------------------------------------------------------------------------------------------------- # -----------------------------------------------------------------------------------------------------------------


def updateGrandient(self): def updateGrandient(self):
@@ -284,6 +298,14 @@ class DigitalPeakMeter(QWidget):
self.fMeterGradient.setColorAt(0.0, self.fMeterColorBase) self.fMeterGradient.setColorAt(0.0, self.fMeterColorBase)
self.fMeterGradient.setColorAt(1.0, self.fMeterColorBase) self.fMeterGradient.setColorAt(1.0, self.fMeterColorBase)


elif self.fMeterStyle == self.STYLE_TUBE:
color = QColor.fromHslF(0.9, 1, 0.6, 1) # Tuneon filled w/ neon + agron
points = 20
for i in range(points + 1):
self.fMeterGradient.setColorAt(((i-0.3)/points % 1.0), color)
self.fMeterGradient.setColorAt(( i /points ), Qt.black)
self.fMeterGradient.setColorAt(((i+0.3)/points % 1.0), color)

self.updateGrandientFinalStop() self.updateGrandientFinalStop()


def updateGrandientFinalStop(self): def updateGrandientFinalStop(self):
@@ -360,6 +382,13 @@ class DigitalPeakMeter(QWidget):
meterPad += 2 meterPad += 2
meterSize -= 2 meterSize -= 2


elif self.fMeterStyle == self.STYLE_TUBE:
painter.setPen(QPen(Qt.NoPen))
painter.setBrush(self.fMeterGradient)
meterPos += 3
meterPad += 6
meterSize -= 6

else: else:
painter.setPen(QPen(self.fMeterBackground, 0)) painter.setPen(QPen(self.fMeterBackground, 0))
painter.setBrush(self.fMeterGradient) painter.setBrush(self.fMeterGradient)


+ 16
- 15
source/frontend/widgets/paramspinbox.py View File

@@ -25,7 +25,7 @@ elif qt_config == 6:
import ui_inputdialog_value import ui_inputdialog_value


from carla_backend import CARLA_OS_MAC from carla_backend import CARLA_OS_MAC
from carla_shared import countDecimalPoints
from carla_shared import countDecimalPoints, getPrefixSuffix, strLim


# ------------------------------------------------------------------------------------------------------------ # ------------------------------------------------------------------------------------------------------------
# Get a fixed value within min/max bounds # Get a fixed value within min/max bounds
@@ -46,13 +46,21 @@ def geFixedValue(name, value, minimum, maximum):
# Custom InputDialog with Scale Points support # Custom InputDialog with Scale Points support


class CustomInputDialog(QDialog): class CustomInputDialog(QDialog):
def __init__(self, parent, label, current, minimum, maximum, step, stepSmall, scalePoints, prefix, suffix):
def __init__(self, parent, label, current, minimum, maximum, step, stepSmall, scalePoints, prefix, suffix, unit=""):
QDialog.__init__(self, parent) QDialog.__init__(self, parent)
self.ui = ui_inputdialog_value.Ui_Dialog() self.ui = ui_inputdialog_value.Ui_Dialog()
self.ui.setupUi(self) self.ui.setupUi(self)


decimals = countDecimalPoints(step, stepSmall)
self.ui.label.setText(label)
if not (unit == ""):
prefix, suffix = getPrefixSuffix(unit)

if unit == "%":
decimals = 1
else:
decimals = countDecimalPoints(step, stepSmall)

# self.ui.label.setText(label + " [" + strRound(self, minimum, decimals) + "..." + strRound(self, maximum, decimals) + "]")
self.ui.label.setText(label + " [" + strLim(minimum) + "..." + strLim(maximum) + "]")
self.ui.doubleSpinBox.setDecimals(decimals) self.ui.doubleSpinBox.setDecimals(decimals)
self.ui.doubleSpinBox.setRange(minimum, maximum) self.ui.doubleSpinBox.setRange(minimum, maximum)
self.ui.doubleSpinBox.setSingleStep(step) self.ui.doubleSpinBox.setSingleStep(step)
@@ -361,14 +369,7 @@ class ParamSpinBox(QAbstractSpinBox):
self.fStepLarge = value self.fStepLarge = value


def setLabel(self, label): def setLabel(self, label):
prefix = ""
suffix = label.strip()

if suffix == "(coef)":
prefix = "* "
suffix = ""
else:
suffix = " " + suffix
prefix, suffix = getPrefixSuffix(label)


self.fLabelPrefix = prefix self.fLabelPrefix = prefix
self.fLabelSuffix = suffix self.fLabelSuffix = suffix
@@ -533,16 +534,16 @@ class ParamSpinBox(QAbstractSpinBox):
pass pass


menu = QMenu(self) menu = QMenu(self)
actReset = menu.addAction(self.tr("Reset (%f)" % self.fDefault))
actReset = menu.addAction(self.tr("Reset (" + strLim(self.fDefault) + ")"))
actRandom = menu.addAction(self.tr("Random")) actRandom = menu.addAction(self.tr("Random"))
menu.addSeparator() menu.addSeparator()
actCopy = menu.addAction(self.tr("Copy (%f)" % self.fValue))
actCopy = menu.addAction(self.tr("Copy (" + strLim(self.fValue) + ")"))


if pasteValue is None: if pasteValue is None:
actPaste = menu.addAction(self.tr("Paste")) actPaste = menu.addAction(self.tr("Paste"))
actPaste.setEnabled(False) actPaste.setEnabled(False)
else: else:
actPaste = menu.addAction(self.tr("Paste (%f)" % pasteValue))
actPaste = menu.addAction(self.tr("Paste (" + strLim(pasteValue) + ")"))


menu.addSeparator() menu.addSeparator()




+ 10
- 4
source/frontend/widgets/racklistwidget.py View File

@@ -13,11 +13,11 @@ import os
from qt_compat import qt_config from qt_compat import qt_config


if qt_config == 5: if qt_config == 5:
from PyQt5.QtCore import Qt, QSize, QRect, QEvent
from PyQt5.QtCore import QT_VERSION, Qt, QSize, QRect, QEvent
from PyQt5.QtGui import QColor, QPainter, QPixmap from PyQt5.QtGui import QColor, QPainter, QPixmap
from PyQt5.QtWidgets import QAbstractItemView, QListWidget, QListWidgetItem, QMessageBox from PyQt5.QtWidgets import QAbstractItemView, QListWidget, QListWidgetItem, QMessageBox
elif qt_config == 6: elif qt_config == 6:
from PyQt6.QtCore import Qt, QSize, QRect, QEvent
from PyQt6.QtCore import QT_VERSION, Qt, QSize, QRect, QEvent, QPoint # QPoint is for Qt6 only.
from PyQt6.QtGui import QColor, QPainter, QPixmap from PyQt6.QtGui import QColor, QPainter, QPixmap
from PyQt6.QtWidgets import QAbstractItemView, QListWidget, QListWidgetItem, QMessageBox from PyQt6.QtWidgets import QAbstractItemView, QListWidget, QListWidgetItem, QMessageBox


@@ -291,7 +291,10 @@ class RackListWidget(QListWidget):


event.acceptProposedAction() event.acceptProposedAction()


tryItem = self.itemAt(event.pos())
if QT_VERSION < 0x60000:
tryItem = self.itemAt(event.pos())
else:
tryItem = self.itemAt(QPoint(int(event.position().x()), int(event.position().y())))


if tryItem is not None: if tryItem is not None:
self.setCurrentRow(tryItem.getPluginId()) self.setCurrentRow(tryItem.getPluginId())
@@ -318,7 +321,10 @@ class RackListWidget(QListWidget):
if not urls: if not urls:
return return


tryItem = self.itemAt(event.pos())
if QT_VERSION < 0x60000:
tryItem = self.itemAt(event.pos())
else:
tryItem = self.itemAt(QPoint(int(event.position().x()), int(event.position().y())))


if tryItem is not None: if tryItem is not None:
pluginId = tryItem.getPluginId() pluginId = tryItem.getPluginId()


+ 839
- 224
source/frontend/widgets/scalabledial.py
File diff suppressed because it is too large
View File


+ 217
- 98
source/frontend/xycontroller-ui View File

@@ -10,17 +10,21 @@ from qt_compat import qt_config
if qt_config == 5: if qt_config == 5:
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QT_VERSION, Qt, QPointF, QRectF, QSize, QTimer from PyQt5.QtCore import pyqtSignal, pyqtSlot, QT_VERSION, Qt, QPointF, QRectF, QSize, QTimer
from PyQt5.QtGui import QColor, QPainter, QPen from PyQt5.QtGui import QColor, QPainter, QPen
from PyQt5.QtWidgets import QGraphicsScene, QGraphicsSceneMouseEvent, QMainWindow
from PyQt5.QtWidgets import QGraphicsScene, QGraphicsSceneMouseEvent, QMainWindow, QApplication
elif qt_config == 6: elif qt_config == 6:
from PyQt6.QtCore import pyqtSignal, pyqtSlot, QT_VERSION, Qt, QPointF, QRectF, QSize, QTimer from PyQt6.QtCore import pyqtSignal, pyqtSlot, QT_VERSION, Qt, QPointF, QRectF, QSize, QTimer
from PyQt6.QtGui import QColor, QPainter, QPen from PyQt6.QtGui import QColor, QPainter, QPen
from PyQt6.QtWidgets import QGraphicsScene, QGraphicsSceneMouseEvent, QMainWindow
from PyQt6.QtWidgets import QGraphicsScene, QGraphicsSceneMouseEvent, QMainWindow, QApplication

import ctypes
from time import sleep


# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
# Imports (Custom) # Imports (Custom)


from carla_shared import * from carla_shared import *
from carla_utils import * from carla_utils import *
from widgets.scalabledial import ScalableDial


import ui_xycontroller import ui_xycontroller


@@ -32,15 +36,21 @@ from externalui import ExternalUI
from widgets.paramspinbox import ParamSpinBox from widgets.paramspinbox import ParamSpinBox


# ------------------------------------------------------------------------------------------------------------ # ------------------------------------------------------------------------------------------------------------

XYCONTROLLER_PARAMETER_X = 0
XYCONTROLLER_PARAMETER_Y = 1
XYCONTROLLER_PARAMETER_SMOOTH = 0
XYCONTROLLER_PARAMETER_LINEAR = 1
XYCONTROLLER_PARAMETER_SPEED = 2
XYCONTROLLER_PARAMETER_REVERSEY = 3
XYCONTROLLER_PARAMETER_X = 4
XYCONTROLLER_PARAMETER_Y = 5
XYCONTROLLER_PARAMETER_OUT_X = 6
XYCONTROLLER_PARAMETER_OUT_Y = 7


# ------------------------------------------------------------------------------------------------------------ # ------------------------------------------------------------------------------------------------------------


class XYGraphicsScene(QGraphicsScene): class XYGraphicsScene(QGraphicsScene):
# signals # signals
cursorMoved = pyqtSignal(float,float) cursorMoved = pyqtSignal(float,float)
knobsUpdate = pyqtSignal(float,float)


def __init__(self, parent): def __init__(self, parent):
QGraphicsScene.__init__(self, parent) QGraphicsScene.__init__(self, parent)
@@ -52,9 +62,20 @@ class XYGraphicsScene(QGraphicsScene):
self.m_channels = [] self.m_channels = []
self.m_mouseLock = False self.m_mouseLock = False
self.m_smooth = False self.m_smooth = False
self.m_linear = False
self.m_speed = 8.0 # 1.0 to 100.0
self.m_rSmooth = False # Using right button
self.m_smooth_x = 0.0 self.m_smooth_x = 0.0
self.m_smooth_y = 0.0 self.m_smooth_y = 0.0


self.reverseY = 1.0 # -1.0 when reversed

self.xpPrev = 0.0
self.ypPrev = 0.0

self.time: ctypes.c_uint64 = 0 # I sure just int is quite enough here, but...
self.prevTime: ctypes.c_uint64 = 0

self.setBackgroundBrush(Qt.black) self.setBackgroundBrush(Qt.black)


cursorPen = QPen(QColor(255, 255, 255), 2) cursorPen = QPen(QColor(255, 255, 255), 2)
@@ -87,31 +108,31 @@ class XYGraphicsScene(QGraphicsScene):
self.m_lineV.setX(posX) self.m_lineV.setX(posX)


if forward: if forward:
value = posX / (self.p_size.x() + self.p_size.width());
value = posX / (self.p_size.x() + self.p_size.width())
self.sendMIDI(value, None) self.sendMIDI(value, None)
else: else:
self.m_smooth_x = posX;
self.m_smooth_x = posX


def setPosY(self, y: float, forward: bool = True): def setPosY(self, y: float, forward: bool = True):
if self.m_mouseLock: if self.m_mouseLock:
return;
return


posY = y * (self.p_size.y() + self.p_size.height())
posY = y * (self.p_size.y() + self.p_size.height()) * self.reverseY
self.m_cursor.setPos(self.m_cursor.x(), posY) self.m_cursor.setPos(self.m_cursor.x(), posY)
self.m_lineH.setY(posY) self.m_lineH.setY(posY)


if forward: if forward:
value = posY / (self.p_size.y() + self.p_size.height())
value = posY / (self.p_size.y() + self.p_size.height()) * self.reverseY
self.sendMIDI(None, value) self.sendMIDI(None, value)
else: else:
self.m_smooth_y = posY self.m_smooth_y = posY


def setSmooth(self, smooth: bool):
self.m_smooth = smooth

def setSmoothValues(self, x: float, y: float): def setSmoothValues(self, x: float, y: float):
self.m_smooth_x = x * (self.p_size.x() + self.p_size.width());
self.m_smooth_y = y * (self.p_size.y() + self.p_size.height());
self.m_smooth_x = x * (self.p_size.x() + self.p_size.width())
self.m_smooth_y = y * (self.p_size.y() + self.p_size.height()) * self.reverseY

def setReverseY(self, rev: bool):
self.reverseY = 1 - (int(rev) * 2) # 1.0 or -1.0


# ------------------------------------------------------------------- # -------------------------------------------------------------------


@@ -119,82 +140,132 @@ class XYGraphicsScene(QGraphicsScene):
self.p_size.setRect(-(float(size.width())/2), self.p_size.setRect(-(float(size.width())/2),
-(float(size.height())/2), -(float(size.height())/2),
size.width(), size.width(),
size.height());
size.height())

def updatePos(self, pos: QPointF, filterSame: bool = False, knobsOnly: bool = False):
xp = pos.x() / (self.p_size.x() + self.p_size.width())
yp = pos.y() / (self.p_size.y() + self.p_size.height()) * self.reverseY
if knobsOnly:
self.knobsUpdate.emit(xp * 100, yp * 100)
return

self.m_cursor.setPos(pos)
self.m_lineH.setY(pos.y())
self.m_lineV.setX(pos.x())

# Set 0.05% precision, yet exact final value settling, esp. zero
xp = round(xp * 1000) / 1000
yp = round(yp * 1000) / 1000

self.sendMIDI(xp, yp, filterSame)
self.cursorMoved.emit(xp, yp)


def updateSmooth(self):
if not self.m_smooth:
def updateSmooth(self, time):
if not (self.m_smooth or self.m_rSmooth):
return return


if self.m_cursor.x() == self.m_smooth_x and self.m_cursor.y() == self.m_smooth_y:
dx = self.m_smooth_x - self.m_cursor.x()
dy = self.m_smooth_y - self.m_cursor.y()

if dx == dy == 0:
return return


same = 0 same = 0
if abs(self.m_cursor.x() - self.m_smooth_x) <= 0.0005:
if abs(dx) <= 0.0005:
self.m_smooth_x = self.m_cursor.x() self.m_smooth_x = self.m_cursor.x()
same += 1 same += 1


if abs(self.m_cursor.y() - self.m_smooth_y) <= 0.0005:
if abs(dy) <= 0.0005:
self.m_smooth_y = self.m_cursor.y() self.m_smooth_y = self.m_cursor.y()
same += 1 same += 1


if same == 2: if same == 2:
return return


newX = float(self.m_smooth_x + self.m_cursor.x()*7) / 8
newY = float(self.m_smooth_y + self.m_cursor.y()*7) / 8
pos = QPointF(newX, newY)
speed = self.m_speed


self.m_cursor.setPos(pos)
self.m_lineH.setY(pos.y())
self.m_lineV.setX(pos.x())
mod = QApplication.keyboardModifiers()
if (mod & Qt.ControlModifier):
speed /= 2
elif (mod & Qt.ShiftModifier):
speed *= 2


xp = pos.x() / (self.p_size.x() + self.p_size.width())
yp = pos.y() / (self.p_size.y() + self.p_size.height())
if self.m_linear:
newX = self.m_cursor.x() + max(min(dx / speed, 1), -1) * speed
newY = self.m_cursor.y() + max(min(dy / speed, 1), -1) * speed
else:
precision = 64 / speed
newX = float(self.m_smooth_x + self.m_cursor.x()*(precision-1)) / precision
newY = float(self.m_smooth_y + self.m_cursor.y()*(precision-1)) / precision


self.sendMIDI(xp, yp)
self.cursorMoved.emit(xp, yp)
pos = QPointF(newX, newY)

self.updatePos(pos, ((time - self.prevTime) == 1)) # Continuous calls or Not

self.prevTime = time


# ------------------------------------------------------------------- # -------------------------------------------------------------------


def handleMousePos(self, pos: QPointF):
def handleMousePos(self, event):

if (event.buttons() & Qt.MiddleButton):
pos = QPointF(0, 0)
else:
pos = QPointF(event.scenePos())

if not self.p_size.contains(pos): if not self.p_size.contains(pos):
if pos.x() < self.p_size.x(): if pos.x() < self.p_size.x():
pos.setX(self.p_size.x()) pos.setX(self.p_size.x())
elif pos.x() > (self.p_size.x() + self.p_size.width()): elif pos.x() > (self.p_size.x() + self.p_size.width()):
pos.setX(self.p_size.x() + self.p_size.width());
pos.setX(self.p_size.x() + self.p_size.width())


if pos.y() < self.p_size.y(): if pos.y() < self.p_size.y():
pos.setY(self.p_size.y()) pos.setY(self.p_size.y())
elif pos.y() > (self.p_size.y() + self.p_size.height()): elif pos.y() > (self.p_size.y() + self.p_size.height()):
pos.setY(self.p_size.y() + self.p_size.height()) pos.setY(self.p_size.y() + self.p_size.height())


self.updatePos(pos, knobsOnly=True)

self.m_smooth_x = pos.x() self.m_smooth_x = pos.x()
self.m_smooth_y = pos.y() self.m_smooth_y = pos.y()


if not self.m_smooth:
self.m_cursor.setPos(pos)
self.m_lineH.setY(pos.y())
self.m_lineV.setX(pos.x())
self.m_rSmooth = event.buttons() & Qt.RightButton


xp = pos.x() / (self.p_size.x() + self.p_size.width());
yp = pos.y() / (self.p_size.y() + self.p_size.height());
# When not smooth, update each time;
# (commented-out part) When smooth, update (re-send same) if click position is same (when smoothing not sends anything). (To match non-smoothed behaviour)
if (not (self.m_smooth or self.m_rSmooth)): # or (abs(self.m_cursor.x() - self.m_smooth_x) <= 0.0005 and abs(self.m_cursor.y() - self.m_smooth_y) <= 0.0005):
self.updatePos(pos)


self.sendMIDI(xp, yp)
self.cursorMoved.emit(xp, yp)

def sendMIDI(self, xp, yp):
def sendMIDI(self, xp, yp, filterSame = False):
rate = float(0xff) / 4 rate = float(0xff) / 4
msgd = ["cc2" if xp is not None and yp is not None else "cc"]
prefix = []
msgd = []


if xp is not None: if xp is not None:
msgd.append(self.cc_x)
msgd.append(int(xp * rate + rate))
value = int(xp * rate + rate)
if not (filterSame and (value == self.xpPrev)):
prefix = ["cc"]

msgd.append(self.cc_x)
msgd.append(value)

self.xpPrev = value


if yp is not None: if yp is not None:
msgd.append(self.cc_y)
msgd.append(int(yp * rate + rate))
value = int(yp * rate + rate)
if not (filterSame and (value == self.ypPrev)):
if prefix == []:
prefix = ["cc"]
else:
prefix = ["cc2"]

msgd.append(self.cc_y)
msgd.append(value)


self.rparent.send(msgd)
self.ypPrev = value

if not (prefix == []):
self.rparent.send(prefix + msgd)


# ------------------------------------------------------------------- # -------------------------------------------------------------------


@@ -206,13 +277,13 @@ class XYGraphicsScene(QGraphicsScene):


def mousePressEvent(self, event: QGraphicsSceneMouseEvent): def mousePressEvent(self, event: QGraphicsSceneMouseEvent):
self.m_mouseLock = True self.m_mouseLock = True
self.handleMousePos(event.scenePos())
self.handleMousePos(event)
self.rparent.setCursor(Qt.CrossCursor) self.rparent.setCursor(Qt.CrossCursor)
QGraphicsScene.mousePressEvent(self, event);
QGraphicsScene.mousePressEvent(self, event)


def mouseMoveEvent(self, event: QGraphicsSceneMouseEvent): def mouseMoveEvent(self, event: QGraphicsSceneMouseEvent):
self.handleMousePos(event.scenePos())
QGraphicsScene.mouseMoveEvent(self, event);
self.handleMousePos(event)
QGraphicsScene.mouseMoveEvent(self, event)


def mouseReleaseEvent(self, event: QGraphicsSceneMouseEvent): def mouseReleaseEvent(self, event: QGraphicsSceneMouseEvent):
self.m_mouseLock = False self.m_mouseLock = False
@@ -231,15 +302,22 @@ class XYControllerUI(ExternalUI, QMainWindow):


self.fSaveSizeNowChecker = -1 self.fSaveSizeNowChecker = -1


self.isXActual = False
self.isYActual = False

# --------------------------------------------------------------- # ---------------------------------------------------------------
# Set-up GUI stuff # Set-up GUI stuff


self.scene = XYGraphicsScene(self) self.scene = XYGraphicsScene(self)


self.ui.dial_x.setImage(2)
self.ui.dial_y.setImage(2)
self.ui.dial_x.setLabel("X")
self.ui.dial_y.setLabel("Y")
# Now knobs are QWidgets, not QDials.
self.ui.dial_x = ScalableDial(self.ui.dial_x, 0, 400, 0, -100, 100, "X", 64, -1, "%", "tube", 1, {})
self.ui.dial_y = ScalableDial(self.ui.dial_y, 1, 400, 0, -100, 100, "Y", 64, -1, "%", "tube", 1, {})

# These are outputs (7-seg displays).
self.ui.dial_out_x = ScalableDial(self.ui.dial_out_x, 2, 400, 0, -100, 100, "Out X", 64, -1, "%", "tube", 1, {'Auto7segWidth':1, }, isOutput=True)
self.ui.dial_out_y = ScalableDial(self.ui.dial_out_y, 3, 400, 0, -100, 100, "Out Y", 64, -1, "%", "tube", 1, {'Auto7segWidth':1, }, isOutput=True)

self.ui.keyboard.setOctaves(10) self.ui.keyboard.setOctaves(10)


self.ui.graphicsView.setScene(self.scene) self.ui.graphicsView.setScene(self.scene)
@@ -262,6 +340,7 @@ class XYControllerUI(ExternalUI, QMainWindow):
# Connect actions to functions # Connect actions to functions


self.scene.cursorMoved.connect(self.slot_sceneCursorMoved) self.scene.cursorMoved.connect(self.slot_sceneCursorMoved)
self.scene.knobsUpdate.connect(self.slot_setKnobs)


self.ui.keyboard.noteOn.connect(self.slot_noteOn) self.ui.keyboard.noteOn.connect(self.slot_noteOn)
self.ui.keyboard.noteOff.connect(self.slot_noteOff) self.ui.keyboard.noteOff.connect(self.slot_noteOff)
@@ -271,6 +350,7 @@ class XYControllerUI(ExternalUI, QMainWindow):
self.ui.dial_x.realValueChanged.connect(self.slot_knobValueChangedX) self.ui.dial_x.realValueChanged.connect(self.slot_knobValueChangedX)
self.ui.dial_y.realValueChanged.connect(self.slot_knobValueChangedY) self.ui.dial_y.realValueChanged.connect(self.slot_knobValueChangedY)



if QT_VERSION >= 0x60000: if QT_VERSION >= 0x60000:
self.ui.cb_control_x.currentTextChanged.connect(self.slot_checkCC_X) self.ui.cb_control_x.currentTextChanged.connect(self.slot_checkCC_X)
self.ui.cb_control_y.currentTextChanged.connect(self.slot_checkCC_Y) self.ui.cb_control_y.currentTextChanged.connect(self.slot_checkCC_Y)
@@ -308,17 +388,23 @@ class XYControllerUI(ExternalUI, QMainWindow):


# ------------------------------------------------------------------- # -------------------------------------------------------------------


def setSmooth(self, smooth: bool):
self.scene.m_smooth = smooth

x = self.ui.dial_x.rvalue() / 100
y = self.ui.dial_y.rvalue() / 100
if smooth:
self.scene.setSmoothValues(x, y)
else:
self.scene.setPosX(x, True)
self.scene.setPosY(y, True)
self.slot_sceneCursorMoved(x, y)

@pyqtSlot() @pyqtSlot()
def slot_updateScreen(self): def slot_updateScreen(self):
self.ui.graphicsView.centerOn(0, 0) self.ui.graphicsView.centerOn(0, 0)
self.scene.updateSize(self.ui.graphicsView.size()) self.scene.updateSize(self.ui.graphicsView.size())


dial_x = self.ui.dial_x.rvalue()
dial_y = self.ui.dial_y.rvalue()
self.scene.setPosX(dial_x / 100, False)
self.scene.setPosY(dial_y / 100, False)
self.scene.setSmoothValues(dial_x / 100, dial_y / 100)

@pyqtSlot(int) @pyqtSlot(int)
def slot_noteOn(self, note): def slot_noteOn(self, note):
self.send(["note", True, note]) self.send(["note", True, note])
@@ -328,16 +414,34 @@ class XYControllerUI(ExternalUI, QMainWindow):
self.send(["note", False, note]) self.send(["note", False, note])


@pyqtSlot(float) @pyqtSlot(float)
def slot_knobValueChangedX(self, x: float):
self.sendControl(XYCONTROLLER_PARAMETER_X, x)
self.scene.setPosX(x / 100, True)
self.scene.setSmoothValues(x / 100, self.ui.dial_y.rvalue() / 100)
def slot_knobValueChangedX(self, x:float, external:bool=False, firstRun:bool=False):
if not external:
self.sendControl(XYCONTROLLER_PARAMETER_X, x)
else:
self.ui.dial_x.setValue(x, False)

if (not self.scene.m_smooth) or firstRun:
self.sendControl(XYCONTROLLER_PARAMETER_OUT_X, x)
self.ui.dial_out_x.setValue(x, False)
self.scene.setPosX(x / 100, True)

else:
self.scene.setSmoothValues(x / 100, self.ui.dial_y.rvalue() / 100)


@pyqtSlot(float) @pyqtSlot(float)
def slot_knobValueChangedY(self, y: float):
self.sendControl(XYCONTROLLER_PARAMETER_Y, y)
self.scene.setPosY(y / 100, True)
self.scene.setSmoothValues(self.ui.dial_x.rvalue() / 100, y / 100)
def slot_knobValueChangedY(self, y:float, external:bool=False, firstRun:bool=False):
if not external:
self.sendControl(XYCONTROLLER_PARAMETER_Y, y)
else:
self.ui.dial_y.setValue(y, False)

if (not self.scene.m_smooth) or firstRun:
self.sendControl(XYCONTROLLER_PARAMETER_OUT_Y, y)
self.ui.dial_out_y.setValue(y, False)
self.scene.setPosY(y / 100, True)

else:
self.scene.setSmoothValues(self.ui.dial_x.rvalue() / 100, y / 100)


@pyqtSlot(str) @pyqtSlot(str)
def slot_checkCC_X(self, text: str): def slot_checkCC_X(self, text: str):
@@ -422,20 +526,16 @@ class XYControllerUI(ExternalUI, QMainWindow):


@pyqtSlot(bool) @pyqtSlot(bool)
def slot_setSmooth(self, smooth): def slot_setSmooth(self, smooth):
self.scene.setSmooth(smooth)
self.setSmooth(smooth)
self.sendConfigure("smooth", "yes" if smooth else "no") self.sendConfigure("smooth", "yes" if smooth else "no")

if smooth:
dial_x = self.ui.dial_x.rvalue()
dial_y = self.ui.dial_y.rvalue()
self.scene.setSmoothValues(dial_x / 100, dial_y / 100)
self.sendControl(XYCONTROLLER_PARAMETER_SMOOTH, int(smooth))


@pyqtSlot(float, float) @pyqtSlot(float, float)
def slot_sceneCursorMoved(self, xp: float, yp: float): def slot_sceneCursorMoved(self, xp: float, yp: float):
self.ui.dial_x.setValue(xp * 100, False)
self.ui.dial_y.setValue(yp * 100, False)
self.sendControl(XYCONTROLLER_PARAMETER_X, xp * 100)
self.sendControl(XYCONTROLLER_PARAMETER_Y, yp * 100)
self.ui.dial_out_x.setValue(xp * 100, False)
self.ui.dial_out_y.setValue(yp * 100, False)
self.sendControl(XYCONTROLLER_PARAMETER_OUT_X, xp * 100)
self.sendControl(XYCONTROLLER_PARAMETER_OUT_Y, yp * 100)


@pyqtSlot(bool) @pyqtSlot(bool)
def slot_showKeyboard(self, yesno): def slot_showKeyboard(self, yesno):
@@ -443,21 +543,47 @@ class XYControllerUI(ExternalUI, QMainWindow):
self.sendConfigure("show-midi-keyboard", "yes" if yesno else "no") self.sendConfigure("show-midi-keyboard", "yes" if yesno else "no")
QTimer.singleShot(0, self.slot_updateScreen) QTimer.singleShot(0, self.slot_updateScreen)


@pyqtSlot(float, float)
def slot_setKnobs(self, x, y):
self.ui.dial_x.setValue(x, False)
self.ui.dial_y.setValue(y, False)
self.sendControl(XYCONTROLLER_PARAMETER_X, x)
self.sendControl(XYCONTROLLER_PARAMETER_Y, y)

# ------------------------------------------------------------------- # -------------------------------------------------------------------
# DSP Callbacks # DSP Callbacks


# NOTE It called continuously with params 6, 7 (outs, not used here), is this good?
def dspParameterChanged(self, index: int, value: float): def dspParameterChanged(self, index: int, value: float):
if index == XYCONTROLLER_PARAMETER_X:
self.ui.dial_x.setValue(value, False)
self.scene.setPosX(value / 100, False)
if index == XYCONTROLLER_PARAMETER_SMOOTH:
self.ui.cb_smooth.blockSignals(True)
self.ui.cb_smooth.setChecked(bool(value))
self.ui.cb_smooth.blockSignals(False)
self.setSmooth(bool(value))

elif index == XYCONTROLLER_PARAMETER_LINEAR:
self.scene.m_linear = bool(value)

elif index == XYCONTROLLER_PARAMETER_SPEED:
self.scene.m_speed = value

elif index == XYCONTROLLER_PARAMETER_REVERSEY:
self.scene.setReverseY(bool(value))

elif index == XYCONTROLLER_PARAMETER_X:
self.slot_knobValueChangedX(value, True, not self.isXActual)
self.isXActual = True

elif index == XYCONTROLLER_PARAMETER_Y: elif index == XYCONTROLLER_PARAMETER_Y:
self.ui.dial_y.setValue(value, False)
self.scene.setPosY(value / 100, False)
self.slot_knobValueChangedY(value, True, not self.isYActual)
if self.isXActual and not self.isYActual:
# Run it once, BUT when both X & Y are received (they can be shuffled).
self.scene.setSmoothValues(self.ui.dial_x.rvalue() / 100, self.ui.dial_y.rvalue() / 100)
self.isYActual = True

else: else:
return return


self.scene.setSmoothValues(self.ui.dial_x.rvalue() / 100,
self.ui.dial_y.rvalue() / 100)


def dspStateChanged(self, key: str, value: str): def dspStateChanged(self, key: str, value: str):
if key == "guiWidth": if key == "guiWidth":
@@ -480,15 +606,7 @@ class XYControllerUI(ExternalUI, QMainWindow):


elif key == "smooth": elif key == "smooth":
smooth = (value == "yes") smooth = (value == "yes")
self.ui.cb_smooth.blockSignals(True)
self.ui.cb_smooth.setChecked(smooth)
self.ui.cb_smooth.blockSignals(False)
self.scene.setSmooth(smooth)

if smooth:
dial_x = self.ui.dial_x.rvalue()
dial_y = self.ui.dial_y.rvalue()
self.scene.setSmoothValues(dial_x / 100, dial_y / 100)
self.setSmooth(bool(smooth))


elif key == "show-midi-keyboard": elif key == "show-midi-keyboard":
show = (value == "yes") show = (value == "yes")
@@ -587,9 +705,10 @@ class XYControllerUI(ExternalUI, QMainWindow):
QMainWindow.resizeEvent(self, event) QMainWindow.resizeEvent(self, event)


def timerEvent(self, event): def timerEvent(self, event):
self.scene.time += 1
if event.timerId() == self.fIdleTimer: if event.timerId() == self.fIdleTimer:
self.idleExternalUI() self.idleExternalUI()
self.scene.updateSmooth()
self.scene.updateSmooth(self.scene.time)


if self.fSaveSizeNowChecker == 11: if self.fSaveSizeNowChecker == 11:
self.sendConfigure("guiWidth", str(self.width())) self.sendConfigure("guiWidth", str(self.width()))


+ 64
- 3
source/native-plugins/xycontroller.cpp View File

@@ -29,6 +29,10 @@ class XYControllerPlugin : public NativePluginAndUiClass
{ {
public: public:
enum Parameters { enum Parameters {
kParamSmooth,
kParamLinear,
kParamSpeed,
kParamReverseY,
kParamInX, kParamInX,
kParamInY, kParamInY,
kParamOutX, kParamOutX,
@@ -44,6 +48,7 @@ public:
mqueueRT() mqueueRT()
{ {
carla_zeroStruct(params); carla_zeroStruct(params);
params[kParamSpeed] = 8.0f;
carla_zeroStruct(channels); carla_zeroStruct(channels);
channels[0] = true; channels[0] = true;
} }
@@ -92,6 +97,56 @@ protected:
hints |= NATIVE_PARAMETER_IS_OUTPUT; hints |= NATIVE_PARAMETER_IS_OUTPUT;
param.name = "Out Y"; param.name = "Out Y";
break; break;

case kParamSpeed:
param.name = "Speed";
param.unit = "px";
param.ranges.def = 8.0f;
param.ranges.min = 1.0f;
break;
}

if (param.name == nullptr)
{
hints |= NATIVE_PARAMETER_IS_INTEGER | NATIVE_PARAMETER_USES_SCALEPOINTS;
param.unit = nullptr;
param.ranges.min = 0;
param.ranges.max = 1;
param.scalePointCount = 2;

switch (index)
{
case kParamSmooth:
param.name = "Smooth";
{
static const NativeParameterScalePoint scalePoints[2] = {
{ "Thru", 0 },
{ "Smooth", 1 }
};
param.scalePoints = scalePoints;
}
break;
case kParamLinear:
param.name = "Linear";
{
static const NativeParameterScalePoint scalePoints[2] = {
{ "Log", 0 },
{ "Linear", 1 }
};
param.scalePoints = scalePoints;
}
break;
case kParamReverseY:
param.name = "Rev Y";
{
static const NativeParameterScalePoint scalePoints[2] = {
{ "Top to Bottom", 0 },
{ "Bottom to Top", 1 }
};
param.scalePoints = scalePoints;
}
break;
}
} }


param.hints = static_cast<NativeParameterHints>(hints); param.hints = static_cast<NativeParameterHints>(hints);
@@ -113,8 +168,14 @@ protected:
{ {
switch (index) switch (index)
{ {
case kParamSmooth:
case kParamLinear:
case kParamSpeed:
case kParamReverseY:
case kParamInX: case kParamInX:
case kParamInY: case kParamInY:
case kParamOutX:
case kParamOutY:
params[index] = value; params[index] = value;
break; break;
} }
@@ -147,8 +208,8 @@ protected:
void process(const float* const*, float**, const uint32_t, void process(const float* const*, float**, const uint32_t,
const NativeMidiEvent* const midiEvents, const uint32_t midiEventCount) override const NativeMidiEvent* const midiEvents, const uint32_t midiEventCount) override
{ {
params[kParamOutX] = params[kParamInX];
params[kParamOutY] = params[kParamInY];
// params[kParamOutX] = params[kParamInX];
// params[kParamOutY] = params[kParamInY];


if (mqueue.isNotEmpty() && mqueueRT.tryToCopyDataFrom(mqueue)) if (mqueue.isNotEmpty() && mqueueRT.tryToCopyDataFrom(mqueue))
{ {
@@ -266,7 +327,7 @@ static const NativePluginDescriptor notesDesc = {
/* audioOuts */ 0, /* audioOuts */ 0,
/* midiIns */ 1, /* midiIns */ 1,
/* midiOuts */ 1, /* midiOuts */ 1,
/* paramIns */ 2,
/* paramIns */ 6,
/* paramOuts */ 2, /* paramOuts */ 2,
/* name */ "XY Controller", /* name */ "XY Controller",
/* label */ "xycontroller", /* label */ "xycontroller",


Loading…
Cancel
Save