From 4bf5db57c267abf087ad0423826a635c0b1689cc Mon Sep 17 00:00:00 2001 From: falkTX Date: Sun, 27 Jul 2025 14:15:53 +0200 Subject: [PATCH 1/7] Import of jpka changes for rack UI rework Signed-off-by: falkTX --- resources/16x16/add-jack-alt.svgz | Bin 0 -> 7141 bytes resources/16x16/add-jack.svgz | Bin 0 -> 6858 bytes resources/16x16/audio-volume-medium.svgz | Bin 0 -> 2491 bytes resources/16x16/audio-volume-muted-orig.svgz | Bin 0 -> 37970 bytes resources/16x16/audio-volume-muted.svgz | Bin 0 -> 44856 bytes resources/16x16/balance-alt.svgz | Bin 0 -> 11222 bytes resources/16x16/balance-alt2.svgz | Bin 0 -> 11216 bytes resources/16x16/balance.svgz | Bin 0 -> 8578 bytes resources/16x16/compact-alt.svgz | Bin 0 -> 6294 bytes resources/16x16/compact.svgz | Bin 0 -> 14267 bytes resources/16x16/dry.svgz | Bin 0 -> 3292 bytes resources/16x16/emblem-favorite.svgz | Bin 0 -> 3769 bytes resources/16x16/restore-alt.svgz | Bin 0 -> 16597 bytes resources/16x16/restore.svgz | Bin 0 -> 10901 bytes resources/16x16/skin.svgz | Bin 0 -> 8109 bytes resources/16x16/system-shutdown.svgz | Bin 0 -> 6070 bytes resources/16x16/system-turnon.svgz | Bin 0 -> 6698 bytes resources/16x16/view-refresh-purple.svgz | Bin 0 -> 8753 bytes resources/16x16/wet.svgz | Bin 0 -> 5881 bytes resources/resources.qrc | 14 + resources/ui/carla_edit.ui | 250 ++-- resources/ui/carla_host.ui | 100 +- resources/ui/carla_settings.ui | 21 + resources/ui/xycontroller.ui | 104 +- source/backend/CarlaBackend.h | 14 +- source/backend/CarlaHost.h | 7 + source/backend/CarlaPlugin.hpp | 10 + source/backend/CarlaStandalone.cpp | 8 + source/backend/engine/CarlaEngine.cpp | 2 + source/backend/engine/CarlaEngineNative.cpp | 11 + source/backend/engine/CarlaEngineOsc.hpp | 1 + .../backend/engine/CarlaEngineOscHandlers.cpp | 13 + source/backend/engine/CarlaEngineOscSend.cpp | 11 +- source/backend/plugin/CarlaPlugin.cpp | 44 + .../backend/plugin/CarlaPluginFluidSynth.cpp | 32 +- source/backend/plugin/CarlaPluginInternal.cpp | 1 + source/backend/plugin/CarlaPluginInternal.hpp | 1 + .../backend/plugin/CarlaPluginLADSPADSSI.cpp | 53 +- source/backend/plugin/CarlaPluginLV2.cpp | 53 +- source/backend/plugin/CarlaPluginNative.cpp | 55 +- source/frontend/carla_backend.py | 41 +- source/frontend/carla_backend_qtweb.py | 6 + source/frontend/carla_host.py | 160 ++- source/frontend/carla_host_control.py | 6 +- source/frontend/carla_settings.py | 7 + source/frontend/carla_shared.py | 65 +- source/frontend/carla_skin.py | 609 ++++++---- source/frontend/carla_widgets.py | 333 ++++-- source/frontend/dialogs/aboutdialog.cpp | 1 + source/frontend/qt_compat.py | 20 + source/frontend/widgets/collapsablewidget.py | 8 +- source/frontend/widgets/commondial.py | 503 ++++++-- source/frontend/widgets/digitalpeakmeter.py | 45 +- source/frontend/widgets/paramspinbox.py | 31 +- source/frontend/widgets/racklistwidget.py | 14 +- source/frontend/widgets/scalabledial.py | 1064 +++++++++++++---- source/frontend/xycontroller-ui | 315 +++-- source/native-plugins/xycontroller.cpp | 67 +- source/rest/carla-host.cpp | 14 + source/rest/rest-server.cpp | 1 + source/utils/CarlaBackendUtils.hpp | 2 + source/utils/CarlaStateUtils.cpp | 8 + source/utils/CarlaStateUtils.hpp | 1 + 63 files changed, 3188 insertions(+), 938 deletions(-) create mode 100644 resources/16x16/add-jack-alt.svgz create mode 100644 resources/16x16/add-jack.svgz create mode 100644 resources/16x16/audio-volume-medium.svgz create mode 100644 resources/16x16/audio-volume-muted-orig.svgz create mode 100644 resources/16x16/audio-volume-muted.svgz create mode 100644 resources/16x16/balance-alt.svgz create mode 100644 resources/16x16/balance-alt2.svgz create mode 100644 resources/16x16/balance.svgz create mode 100644 resources/16x16/compact-alt.svgz create mode 100644 resources/16x16/compact.svgz create mode 100644 resources/16x16/dry.svgz create mode 100644 resources/16x16/emblem-favorite.svgz create mode 100644 resources/16x16/restore-alt.svgz create mode 100644 resources/16x16/restore.svgz create mode 100644 resources/16x16/skin.svgz create mode 100644 resources/16x16/system-shutdown.svgz create mode 100644 resources/16x16/system-turnon.svgz create mode 100644 resources/16x16/view-refresh-purple.svgz create mode 100644 resources/16x16/wet.svgz diff --git a/resources/16x16/add-jack-alt.svgz b/resources/16x16/add-jack-alt.svgz new file mode 100644 index 0000000000000000000000000000000000000000..3ece5fdacfaf73fb05b53d2af353b730593f8e8f GIT binary patch literal 7141 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 bBfg=y2pz&d@93hfA7A}Hpy$N1RFD7w6K=T$ literal 0 HcmV?d00001 diff --git a/resources/16x16/add-jack.svgz b/resources/16x16/add-jack.svgz new file mode 100644 index 0000000000000000000000000000000000000000..4d821803b8eb80847a76945e5914aa89dac1ea80 GIT binary patch literal 6858 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 F001_U;2!`0 literal 0 HcmV?d00001 diff --git a/resources/16x16/audio-volume-muted-orig.svgz b/resources/16x16/audio-volume-muted-orig.svgz new file mode 100644 index 0000000000000000000000000000000000000000..5580e561ac10bbdeefe0d87346a1573bc9027b25 GIT binary patch literal 37970 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 HQGNvgbH8CN literal 0 HcmV?d00001 diff --git a/resources/16x16/audio-volume-muted.svgz b/resources/16x16/audio-volume-muted.svgz new file mode 100644 index 0000000000000000000000000000000000000000..29547052f4a4e0b02a0816d2d4bc84c4e7a7f9f8 GIT binary patch literal 44856 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 b_<|mc*go@5|M`c1{muUms4$+81%U+snKCkS literal 0 HcmV?d00001 diff --git a/resources/16x16/balance-alt.svgz b/resources/16x16/balance-alt.svgz new file mode 100644 index 0000000000000000000000000000000000000000..dadf303325b6bc323035c13d67cafc48e6f03500 GIT binary patch literal 11222 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 literal 0 HcmV?d00001 diff --git a/resources/16x16/balance-alt2.svgz b/resources/16x16/balance-alt2.svgz new file mode 100644 index 0000000000000000000000000000000000000000..e0600643e60925798c7a232d6dd6f45a2d1e6d98 GIT binary patch literal 11216 zcmXX~V{|33)~z+QJ+(cxZQItZZQHoDZQHi(cIr-TYrc8!eOdd*$v#PT&d!glr3C1boR+^Kfc#f*JB!Qi;!+k3^ z16e<>F6|wI>^%R^SLQw~|8&Mw`&k3o#!DoD&)fU`p{$=2XzwdNPpRUn`|#oR(ODe! z8eNB3Z&kmzRqh7BXa3}V_pQsT9(8_N{TdcKCY(Mh8#Fk(I8cUT&Y>!hr4XYy`0*`u zWoMn1$5?x9esjw3bg{-2`#txL9Q{)D@lv%mQl{ppp&xi%<&^Zv_Sf#i=nHJ5>|?|! zMQ?W-^O&>g*y)bh+dpr2w+F!YwDkFXvXY_PVf*#&{AH~@)fFB+BULwxNDm3+-=3$3 zf%K;(GPQ3v!Q|s(2KsI2;OoA!y;KJMO|OCb?^9i$UEj1QzrhS`+MA2aTJ6k!(aC~Y z%w2ZhPR@>7E~;=g8e5gh$1TO(^zrBW-nwuOZOQ`9#?YUxl}i=3ms^wLG7XI_1O31e zdwz6{X!jdTcO>#VE6O={%H2ML-}g8VRSyTP9va&3-Fn#^;9wn(Yo$9YUd&0qL z?x`HAQH$BuHi9v+dii{K`7pHc4ACk7Ri;hb*Gr}MY}C@en=K>U`0gV^7mD<&^T62i z?vU;8M}~klGS5ymzrlFkoLirku7LnLYkj%TXpmfAe#61Wk0&C!pm54|U% zT!FH%y>xA0fKZRtsz`o0>LW>xZDBOK7=0$CLmH=gM;PzrIO6oU0<*IZaCh(WV`sP7 zV`EMDMr9%t1!8lA5X{$?uPbKc4D8AnDbNnDx=#IaAbQr9EB){@Ere_KC6tSA+!A)6 zWHD@b)xFt;MH|pH9#OPmnn3d`ukZCU%N{*)N$l*T=k!@Oi@sGkE_FezBK+P;uB@+eFgLO*%6Gv z&ckA;WM;tUQCvXrPJ(ilgOB9c-^DOlV03=T~$T*}dd903Q|t_p|7bL1GNv6%piFD-&wW2)LI!gS(Mf{;~wnS8le*A(zC zB{eDEaVq)Xg6v^_CymD*8S4U1Ad;KEYJ!S)hHcx{$YZunrp{t2-wA7jpbr?LXaGj8 zx*=87{bw-7cPO$oXiuvV{eLq_PbA!1&g~xTpRGyj6V_P)(Vy`Diz%ta{9`p1>sbPGxY3tjr*&Yn5nPQ6w_kKV zxS4xVO}Q_7ky&wf9M2rY3G@NUatGSfYS7}`X3R^M6&$dm^gBG!G*Y0rC1P#)>DK>r z<(evB8r-&2!!(tU4T$e*LSK-DP>{cYgzhL_vX8)a=oQUNj-u+XjFv8k@j0~G^mhLZ7I!s}$ zmJ+dCorC~!4E?7jg>MF4;JMFj=+Ie*5^si1OC=YA?m+clgG;g#83tad{kjHSa6=p> zHy!n81T{vCn908;Xt6iqA@?X3de~nD}KG(czY>&WvzrEV|u#m}_;jel{D`8YPYkq7Qs+$V7wOsqufix(mO2GvD`dAXYF){b{DW2gz|RVIk^sefLHY>X3! z3{sTS6r77-D;bsUGeJcq^FC5HinC}dfnk%I!kbr0U5Ahyvd-WOIScvivo;YXny_f| zoP*7yxc0PFqSiqdCNWU{HIvx7Sp8L}GfQ?25LZhqNcQun>diSMiL%Re)UlMb}Sy7ZksqHF0kPMsIe7z zVn2IaoH79^5(+@W3t6f&)1SqSY!ogOZJqD?t``++wq-1MwF*A6_p9tNZHev>SM3yx zp0rTjD%;CwD6mW!nx>%FQB4%@0pLN@1HcMWWt}8ziCRo*(AYfb`-KZtO6VCtfseC^ z7(+DzMhjwN(lb%AA#DPRX*>(kAlzA{OWT1Gd7t{9d6G9P2sUrsdnqK;L^-}9lH<~l z#5oloLLpWIM-8@f4}+R6(8TJQ8fFGw22$63lc$*YrP~I-(J=$WpBENB+sZ`C zqba28fhrx!@4C@c+3%IQZy4DqUzahwrN0XEauOLYW6~`winnl>=Vm3L*V*T0#~t5! zc}M{vPDZN2fR`TB$@r@F7ucs*8Qrv5yA2#(2H#%KoE@|GuXb*!>*&q((!aDsi$q~) z6+cPHZ|=K9i}m9Cv9Y9^>yK3&KY0d~j387zD-VD6LH7Nn9hN*Cw*$VCFfZ1D$Q+Ev z^}oRF9<+15RJ_R$oXSNx9$%etZ4>OtMJesuMrS=fHe}hHS>2n}XOZigceC{|9*Ixi zjq|=?_g_r~!%|DEd@1v((JhFTIifASPtBjvo^~(?)x{V6f!7OVFrxXI&;R3N*W=WB zjg+21OW$2nRteK^imVojL8v$9Xnkhlvr(my)@r$Jn$;hCdQuecJ$+((n}Uq_a*pb$ z__)6Se8~~P5>5a*M|0jr`EoZ-A-3S7V2B-Ez{=H+^}Kc-T`sZh{YZ2!RB+Y*y0UtoPjbj2)YEzUbI2J^=4WuPCb_#Szga3)BQuWH||unB-wKk>Dmg)?uj)A?m1TvV!R#vf*I-Qw$Tq>GDdtSCvGzt-l9GC`G z>|vOb0F4!NQ-lTU_P? zWo?@9WTp&l=?N_Bt-?Fr<=oxUKm7f_uN3-vQ{R`N@T&b`IVLpa$Q?|$ge57W#NBP; z6~j;q8Mu|#b-^&;FK{G4o>-=}EtBTU%BmLfmI@c?dxf6|SUGwuYn^FrY!+es7~3`P_3bC8Z@mLcp3AY2szN!zTTVaj6kj>TX1+RX^YyrDD~AU?dEskl3M-h zI>MT$g_Lv*d&D0hOF?xExzL31=FKBh)&mKoJ-@ah5>^eE+T%bY1mrB64DQE&Y17hw zsh(o@K!PJvKgdGctAQj@E09jRG7G&1zmi2m%k0%gsyKeZ)rGKsMRk&FJ;`^_F; z?tm|f4xRUy_P!h=5|gx=tLb_-bNsoe&)(&A_mre9q5se^eU>KT7V&$RFi3&j|6JoPFvgJ)~{ZWy>wde>7Pc+z?OzCG|ESx@&FP z?hQ6%SXjj`n#MdvDvL{)K;FWbZG*fsqN`m(+oQBLDfmHjsMw~GadF{^H_rV`bghz` z;p!03T)I*C(1G=Q{@!_O3r@z?ow9;0-PM_~0Fw5v%Wq5R(%h0W6Unpn(?63{@R0!S zDoWC2N4l^X`X!yAUIPX?qRr{xAlt%_7rH2iF(S??7u}8?32TjgaLcy&boC0AbwgC_=E08SBER{1a zEID)XXGhRW2R|WI0KRjC%4GGll?Zof+rvT|M@K3gktydycR~$JL*OvVJUdY+Re&nL z@6hGCCKZk#fy754c6`B~@t6IZqq(udK?j z8xARcoEc1DcX|q;4?MC|rb8ks63TEJx629sIXRo3#}&mG$}a2u!-FtW-efHauA?5P zz#m$WUIHVJ!kaXR(o8Y=_VLBojZEmGQ<`e+3hXlpO=2wr$f%twz8O(5 z;1Ca;5;MLfzKp7iotTuAYRD!H|36+?*T}H&;4YeE)S)NNl;%J&9wZ}51#gNsM^;Q( zZ6;q&kLEW0M^hW}(rF|@6#b*a@N$YeqLhFb zPkzF46L5(X#X~@hp&}xQBn+?e;E|1})EYwtCOu{!>%R>yBaVNLj^Dc5{r23EqTgot zzWg0Iyfvl;w$Xq3uj^kpwT5E8G;u&8eVkXqX13J6n!?+njVA!@9X%GiaGVJ)9vxVcePpR2gSk*p!|=6g{)`L>u*Yzxc@U%zbZwMnxpVxbdnGBs@A1*7Hh=IdpNpp4>NlA3t3P-b{1(PS5-~B{S8TcR=rvbzjym|rlZ*u@~$k~ zPVSx}>?@q{z1{GO%GvkCofgLSJ{@tun8Ck9K3Nju zx8y+ONF2g4sVc+1G_7p$(RZB%N@%Px;J!StEnFQ)IGfdm>;>y(b>BO@eYdt#@uRy( zzkU7*UvavNH}>}P)Qm#N>EnlBB4N?f?KP;W%=>ydIJpj@a&PO97mwDUvyUXz!20K( zk2tVicb4B^oBld#rmRJ3A)X-u*6+Q+MvUMO)HzWCd)J0Y$_G9AJ7`ikC(3JTjd-1+6A@NJnIC z>{mVPkXRFsPJzAsEn3D_1x0X$*}v!PnQr5WC89JX zcKF*Ti({@X{qSh1awad0f(2KxV92j1{-x$zA*uHm#O0%#BughdChmpIY;##bZryNy zk#5!zv*I+&v{rUbv7dqF#O1K6{C>d-w<_G2!q9b*xuO)*Lm|k}9eCOc5PbT&BO@ZA;X%>iS2QjuHEu?F&1xioU)^tTQQG^3}~cVL#B&ph|=-lr8@l42+v}k zT-cPD{(h$sa* z;4CPwgv$2q1aKKxt|apZdGa9*^=++;qohRxy{*Bf+o)R`|Gt+^$VLXv*An0p-&^z( z7pOV(#fIM9iyowLl(^D$CzP`Umqf?XhKG<>8ySOM67v{svVo{SGq4ffELp+MmJP{9 z4=xr#swGvH6Zt`MUt}%n1%9HE^QymXab(%@9tHV0daS$<9N%vk7JKZ2)AG`AoMVlZ zm&Pl_V!xg#*APSMBPN3}o-6NNaVqc4#hCRS~g;( z?6t?+cpR;iL$XGr=U~?xky_t$iG8U*Q%**D2-I8ivtB3Nw+dQD_wU1EN=JP9WvIJd9IiY#s% zz?ORgj*4qk6HnHG#>+=bgTYIdAR3hLDY~W0q)gs`X6@p!JumGHXSwBHKiej}=YC`# z(4QYlO;EWQYzVgZEX7cydBFg*DiMLt?<74IN#|D~a~yz@>CXUI9FQ%QE{j+jn-ixM zS5#Z<#e-pp{D68-vXr?OV$rU70<+OA*LlXM?=*1i(6B{1pQwxnR9Onu#9OY(r`GQ# zOsCKlWC}}sqhhYqyaoE;Pfey(>rMPRk}?Vw;4IXOzo@cgHyA!&hSnL65O%cL}sqH-RH5pu&D?*feSlJ)~C+x(0t>PP}peDZ}Pf-d+q!P*Z|QT+|rmbdkV^ z%}otC1Rr`8x(_@NWdGYaqy}9mSY%|a1!OpIusudrlw9Uqao+Uok(TUK zA^!nmp=(0Kh-Iyg&pm0(A7DfYga8>10%3SR8$!{@3b>zwUO}2FzyGvw5y`WKg=UT; z7KAa%$?Wrvxe+g;&I9cjg$ddvZRBz(cM8=FcTTmEedt2hKUhHf-H(Ze7uxU>62@nI zHCMT;9&($(sfi$(NFT@nfbo1|eNui=I$0qIg5M$`0L+W^9t!*#L6d;C?3f?0&7_$C zI_x@fbShNZe%rukD)1Uwz@l<4n5&*@hu)Gq@;aEPJ?ERl8jU+Kg1jb6H*J zG1SPi7PNN?k^(AG5z0mw0+!~al51ROktZ?m6ABXn1A)nO@8(ohk47>ONBAnpIbYgC z3S+AR=&krMrn#hJV3F4nl`>7%$=eT6VwD0w@rY~g--CzDP-l&P0<|kd|L4stR8k8% zMPLnkB5stnNHH4@HIDII(8Kgw%TBRafGs#G3*P6p};Sd83s%a-%1UY>AV5dmvRG zc|4poa=L0zk=YT(eFad&;ur`uld5suL-`;vjjW|dqX#FrjnA2iKVDrZRq&fSS)5JS zyg>r%vG}J+gQO+F1celw9JNyv^!_gvPSdWbemo<5%byyVs*!v>@}oWAGu7zyKQ&?S zae}S_^%daG2{1#jGHgoC5Tg_mK$9?zKzYJ2oNCIEhlYS2mlUPmBvVq8Xx-{H%ZlDcBTNGFeqM0Cw8jW^a4!$VoAC2JQ2Q|K1^ zWZu&h^_<|a{Ugzr1QK)32tY=mYRS(E3?w2;KWP^232gOx;?Pxzy*P}JxDt{*a2>1A zad{(v{RTTk6-VPbucDb~GG_%Vy-T#x_)MUQ=#UcteyW{3IVb~1{VtAG?BuT8DpiKp zZFAqVt}$6g?0sjR1$jb>0TN+49wuCq1{WKSIu%DvkVzYZ8gDST*x;zcl4ub8tcQ$i zMGgxefhU4(V%|FX&7>5fW&CL9lo%R^%tU@9Mq$#T5=A7^n(q|kz67>pyh)mb%=w!Nd!ga1dF5{!bLT4BXgcJ3sZoYPpGjP#2^8&4b&rpf)n)H2!3xJ z)nla5oJ=7kff|a=!VIJUfT8(IkQm&@S#$>l+Qc`2gw05dA==C+qCSo6QSw5VO?VK% zOSGIn&5bD=F%cjDA0*$87ss5x29h}1a1vfDILwF0d%xkbMiiS;O=?IeL_ePY2QQrI z6u3-BIi14qUMdBzD`2E_~d-u1TK?nO!2&4j_jJ_DsbLT9E|7 zPK_BOATe_hl$n-?eTEPuJavAB6(4dgiWEhcUp?5_iZDw-+M=Ca7u76|lrne-=NSi2+!eNNovB=5TV-2gOY6U}gdE|ET!8Yr z@eH>qqb+2M;_f-slh}Un{H=2Q-w(q;VKQ6%$bEQw2jV@^09q(f zp1_Z8ioEDg@q2Y2L3tlsHt!9>+Zp+y{#=$*<@Zfg2&mg1(|Lj>8u;cU5Hk7_A=ZSP zs%IM+4*0^tlQjsW&FFF04P{p|xeawdTbCT}W%qN%KNTW9W#TvdPm=S)A&Ko@>7pad z?K@Y_JdMpq(ztU*1g}OAQl;$3qO0@E{o~Ea(us$Vv33nh%2dx2@Vv0xX#0#pAh{(Z zA+Suut%DHx#>348+kCGEL;kH1Oq+{rcenQA(#R3Tr0<7CVMU_OO4|{}m4S>VCb>02 z%c+G^d}j{6B}31Un;*X2h_3hC^HUd!9CgIo-D^u#i>*qT+N#y(Z91jUf&mmL&;>^y)bx9!=XGg1q z9hTV0)=4>;;Ap;%JJ$|C-Lw85Z9JUY$rYW>B7*aHw|o(8tFss6{q+l8mHjf&fZjEF z`WgdmGooKWhf7zvsu-mgh|S4&v=Gu}m@#g43cLnpjp*13Z5vr<8`-4uB*l9cO-=;u zt%~Rl7P$JDTBuTqZeV^L#SgRv-`Hv-5-mX+eIhd{ipXkpWPJS8h$-n#_(sW0TeQW2 z(S*1A*oxLDi6{kRuV@LI2#KaEUGKvV@R~oP332A&oz2fi7`4*F-`=*Ti`qVFQ1=z! zY+ef`uJ4ubteCx97cXi!q;bU24UoH@tT84JBB^IsvvraP7swI&Y^bfs2rT$VW=D}vp3%P&g{=~DqcB5#~C(`p3rrAJBat4u2%C)q+DNltoRT<2Muwe0`3AY- zd}QBvOj;z%&uhctD^W`zEpOa0HRaG7g^gcf|NA^ktJof8QiLU9o0(-D5l;!FLtK3R zj^h@CppquxJxNlI8-|;HA7Y>5*YQ4RN>QRpu};o#oO*C|?Y<4~hHn7qCAp9`+JarP z6>Nl?L^z*8B1ZwJ0L-wF#}@9NCbj^kWi(2a*s)xAG|r6ToUD0zWO9}nZ<_kMs5UXF zOTU9{lATBi2DY(Mo+D;(i($6Kw8S=-L=ucl_p(HjFgo-L@zttzdZnT#f4Sep5XE~N zsH_X6@Sw1YAxn+p@H&@<3*~LY?DWO2ImxWDqQxe-j>QszC`-Rtl9t+NXdFadVrgcRy$q?4I7ulkGPSYv~ zXcL5my!xb*5#+cwPg1!*T3gnpRYS>baeKKf4b!UYVPnwb6Af5Dbc4S_%&z&3qcyCj zWe9fcbc|m7q<5cG-#=_wkk}@9muZ@Mv`$F57-r*&JDxL_)J7dlEEzUX6WJWfN(uFU ziN+_Vq#*h^!z0>(5p|1{q=mT$eq9~OB~gG5f~=R_)Nh0f!nhfDj^HV-7dSYeH6L5i z3wc3Il@Yt(ZvneQVSw`IxVwpQf%&v!npE}q)~GQcbKT5y>JVtC=~~Zb=DQpN^}8OA ziWdtaGZIKGKM6)>W1$K#)MZA$WBH3Ik&+|6UNRF)TS#l*(N&r91J)DTDjO zO%|J1i&FxD3kF4e0PWz#Nx_NJx`kra8aaSj{?VyIks3g|=^q=pkk=Dd0-%?n1t-r8 z?P)D3+k_HO7k8**HZhA~W(ksRA^PJ~1_9u>;3T#5+y&HUH?<}*l}H%Et(M!Vonw~S z#p0$)--l@@A6nTwjkP*ABca6PVvvlp_Lph~aV(^I>p}kMYL7zdO84NPbmLFu0j}Z! zJp_O`_iki-`#X|ulUa$#d2yIsLkR=J&#sWBvGl|c+X-cqjO-3QO&G0oQL7GM^=l|Z z$`Ze|7+8lSF|>uU<;Qhdj=doltn`h%=Qc$2ERY^^2znka(;{?kT=Q!p#2ay}T?*tb zM09Yd7Ghkz$7k%CyO@KpiPcNB*{w1Xykj%nakjbTZWL*YV?zH@;eVbAW~5WwWldl;>+>PK$47r_gxv=CeJpkLR%2tv4y4q`m2 zjy)?O|H@!!gL0`iLIkZzIdgH9ng2RPZ?rFQgvfff`?$>S*DA9PAqJY9Ximen~uJMf~S-vnph=%s9eM=`!mS#sb*%LK`6n3i;}NSZ@4SxXZm)auF!e5J?)XK zsFE{P%eEK|=KfII`k|JjXy}s@+Dmoou3DW*Zm&&Kirhqu#rm$!RJNxQsmpKOjBxfa%avtHhZw4nON4}bo;Q4>=WU#;>V$- zciQtkqtO}3y&yngHI#y94VYDHIkBDW39l~8e!-MfpP{4NXLrNq#p<|*QN`>iy%bmU*6Hs(6JEnb%TqnyBrNeiAE_-OH;Yg0GM|I2Gbq?& hBNvnxohja}@;Vk^ocqZ?pZ6EZZ{R1)pd2X3{{bwttmOaz literal 0 HcmV?d00001 diff --git a/resources/16x16/balance.svgz b/resources/16x16/balance.svgz new file mode 100644 index 0000000000000000000000000000000000000000..73dfdc7adafde34269a1df230fe1dc6d0b57f351 GIT binary patch literal 8578 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 IHjp6!077)3u>b%7 literal 0 HcmV?d00001 diff --git a/resources/16x16/compact-alt.svgz b/resources/16x16/compact-alt.svgz new file mode 100644 index 0000000000000000000000000000000000000000..e935723c5c0c374afc4172ffc21e220596ee9692 GIT binary patch literal 6294 zcmV;H7-{DpiwFP!000000PQ_%kK;Cy-}5Vcoi7Parpe;FGm`}_!EP4F;SRUS-p9aW ztKGhF+lK9)>7HMIReDmgB-^q*9l+a-!L%q!RV)_k#bW8*UtXRTgHQ2hJ6|o|-wDR= z2Jv#Tn$DNA_jkYl_Sd1i8*F#u<#fDQE#voh%hlaqe*EU$zYT|jpEmJ$7f%PD=ex(j z-fAD^fVW`1(v7N|=_=Dg)8UP116nx12 z_tm#eSCi%V3CA^AJ*~%+9YdI3k}egRRKTpBckAcfAMwjBUZ!1ec)?hc+sTWH#_9{= zm-TA18$QezaSD9&xO$35>-iF?uvy_BlkI5r@;Zx`BYa{p{;-Hgh#bP&jy}(q)79tU z_IbVD#M^B=W$Wdv@%=B;_5A&v=Z!aC!<)zh%k6#M<^5gT6cV;uBvZT{RYsORpA)rs zK5t;#)$?W&Kj153wv2b9pMU$gxH;r(x|;^Cd0F7QD>!_X33TDTA0>Co-c9|0b3Q`y zhp2s;9ocg;eQ1TvVFWP59>eYHayNb%F1OzXk4`4VlM`AR^G`8y)e}xyO1w(vY1w)G zyjdhYrjt>;h@ax+ZaWf8jP8EK{_m#o!)*92WjV?}Zg?;<(k@#b%v@pO*QrTx;L z)jL`nmpy||ZFj46-Zxuo*rK&f`62yTJv?kPg2u8!C5%lQsD{COL1{z0qmzm~sm2CXtf7X~`W}1e_b)4(wbSb#PJRf~23L z7^Y$P#EplEROO@Mg-m%|mPXk_BBK$*R>Th(U_xLE1}Aj#U7M7#RDg(;0a3 z0e`1wQ7Pv53&*em(+zY&yv<=|z+O$u^N`vA`fVNExw8UFov>ug1O4_~G6<`KO6M%l zCf-G8?huvGj@`wZf;zdOiQpIU1m8Ped>+4UD|#z^M5#ddy`U;Y6$@a!b%El{nvFHe ziLyO+9~f6!dtI_b;Ycv9rLM^Phxl*f=k0brUjE18c@qrw5XwZ?>Cf?}`FNMP1i~Vc zb%IsjkE4Ttbm@@08S4-piSc{?xQWq5{kB0cv~#H7%9b=b&cNs}kC*_Vv_%66f05AI zS`aR>npvg2ga|ZeG?h@=OJwXw@0qpAn2KR!!7^^W3JfD??2E0+ab`F|FG+g^V$yo< zB&Oph5|c(RkWdU2K2%0ORK_uMbET>bzo9dkmbeI*HAdTOjz2L0y2lUKV1l zGvRQRMbaqd9rw1%OhYLpN(NW6vW2pep?y>(XG3Rdwbn2r&`>SO*;||m;`f?711)Q! zENm%+nC7NIfKlt^g%_?L)hW1W9I8!n(KL=1b!OYVzXOePWP~l>{qRZw23*;E3_9t#>|*N4%|drH)ZCF87vcLkBJ>olI(c zvQA!ZLcNEr|2Y`q0@Z?pg-9UH_24IL(oAd6&ZSKbk-|otN?e)PMk-VS_t<6`c%cEP z(c{$V!LI}Iioqmr1e~z+l}_W-A%~er;yjbL>nn}GyzxSc@M(#dNs+!&)6^p)L1cIp z2EGKPa2<+6wIsPS6)4U#ZNRw-6pzD6VFaV$j&UywY9ME$laI|Fr4x3*M#+S?5+z*N zD40RJ#fC4y_u?WDDE9Ha4zO!RB_yr!*4i30&jd~r8b6g6quxYG$}5c;?I*-m_zEE z$7oTaehpvg8Bb9vp#R#4)0e@%n}zCOasRwod{+~DNM-r`4|F$hJGQ&c>SK(;XBlUI z4-+ML@A6A}2@1&j&FXnM4IBS&HD6X+pXR8S7IXaXUgd4m@%AxwXztMiaMpV7i+4B0 zN^+rjo-x(n6Y7(~DEfhOzVCh?=@aU%Fv^k8S|V?a(DbD|XDLl!s@6l93Snfp6 zxiQv!WYkAlxD=JNgHx#F1kUb$ME*D`A*|D=G|@*wYoZd`1^i-EQc@n=;y`)aV~gXV zafpYvI8bMt9;>q5(9T_P!0o|J0yNC}Q0cTdYY9z=9#r!CRO-7C)FhYuVJe}*??EN? zpdVZhDk-g{LmQ5TI7KVy{IKV-vRgRWk46+!aEUeSRBclb z?Ly8q*QJA;gey1$GUvDsx>LK>WWEYfcJK6h1$s6vTIjRG*97fy(k-2yue8TU!1Q4{ zQMV>#`4xBhJ_!0fZvRSX%Z+s{< z&IikrM5IH9*=iXOo8+mb#0+g}0bOsIRvJCsj3VzwlrWS0RYtzXPx18AJpTMml?v0< zzC}+>+vj3rV6Q$vRQWH!)h~)H~Fn}%~oz@vsosQ1>oqd#eLpNmfvR!ZiY>ptdwHd{ilCqd!9x$DiM&PYYqd3`Vku!(oB3?Md^UB zMG~2BBpnAYED}5t&M;xY`R_~fY1Z)u#)U$@OgE~_t0DSs%;OZI%f_7J$a3G;2|b-} z*SH4m=S!l%z6X;nEL2fdn~Ii=`mKFLU6Z*Aj>wH*v?tusF3>dZIj&((;oyd-CjHyfmP2&a?ZJLszn z1KJL(&=ozDCw=oJ~B@>FotU*NCaEuC#erT!3sA-rS`X3~)Idxb%VB@=`)UHKP178`4R(#% z05P;(PC=5+>r6#n2`(rhiJv8}tPb{_Zbe?z=}Tu81rfMPUuo&EggTeL`sV06tMRt& zD}2!fw|Et42TXG-^xNx$4wSOdx+Y3o&@1q0z0mP$L;?AhPR;D2AloOy=}QB>iatPt zn;9)YM5uh|kb}QilxXJY9Mzu%yGDKBDrceU<|}CXXO|x2_40 z1pPDt9UDcqS3aW5Q6Uuk33dxQP|8PpU0tA1`go1YK5}RYYAx#8$O~vy1zyNLs-O$z zRWt!;I?N|jMEj&%>IT)*R};uT5ps>1KpNF9s&FE-vp%Sxl$-=sM0U&1+BcSAD(S6= zs=6kybVAHR-!PCy_uMcbsgoGsI5}u~8SdX)5psC@)=2wV@9wjW=iI8W4xw-tNbq z6UnRqa5c+8A=NxT%XA>}-kxPPtMhh{6?vkj2C6ZEnp#nf3#;GWi|j(F7;e+v&Lyn} z&749v1{JwM9#Zv$2STCAfGKX;3qw86kyJkU$z*PNWs@?sPADl%M0r0-+C3vla4M`+ z9#)udCX3)$!9FaSR!$XM@={9K)zDanwui*ANmQBM1&{_snIQ0$>b|u>r%Sa(i#vXN z*$}kSJF zXz4IyHDQ7q8`OP_8?@3~z)Yjr8Je7G&2cG|jV#VTr!&|WGc*v*Gm}^@h+U(dff%+Y zu}~QubcUdsb0>^vO1txN?oe$0D~CmRW~h!ybyFC)W`et*hDSg8-kudVtFgB48yE%@ zxgaByGYr+5eDht)E{xK#dVb{$7EsJ;G>Ri5IGtbK%V0qiS~FPiUat(6E}++lRU3{L z26tLTbh^ZW7MDw40WX^9^CrkOs{$|D4><{AnY2{oyCSL#2*pr_f$W$I*|l%b2j=A>)$Ji`=>j2@p0U=V2sTFsQqQyc=XR>Z|Bs+M@CnvdeQV}$diOV!5PMwB1tnxa2 zDveDSz=kQbG%AkZY~IrqSQZUm22TSze@%^aklTSvkdR5g1PktkL4@%yPqk2K#1b3? z*A{u1u%IKGQa}-z7t$C)!X>*eX+K9)3?Fn9nonb_DTxUlmC`Od4s(!UOZx0T2iQ)E z(-%WWs1%k@fXck5AJiW8Cq?BQoEeozroCi&(DOxS)8a%60Br!387BGFP)o^#bx1nl zWb(0si&;%dh+`x5S4XbVYC;6tbfZKEM_6~ud zt86ugs#R(^u3%h~M>){#C_M^d>#TE`s&?L>{YiQD0x0boSDdB-B+~ag zjoia(>V;80)=P!xLorn1fF5uXhqaMbqy)9BXZ<1yts#26*Bznn0rWioxkcDYq_5=) z)hg9Dr)uDrR0WNvHs1!j7G0n^P6TSIX`?i+?kXxO*niZPVymRmx98y1Y3p!erKsrv z&8arLIaAH*+f%c3;nSI2)GBqQ2g5YQtE|>kkpF9)1nh%QHd1q`C~C7nrt%y{2=M5~ zYV_5V-q!-AHHwP&x}&H)fL=ulC|r27kxJ)n95a@qH+4ZZV2_QXeg*Ve^nmRu7X^}0 z&{S516@4F!zEyFSuyQvaN*&@FM*3(~-L%wJGbO3|&spSEH%H#O2y$HoiaJCurPw;k z6-lI#g11)&9T;U}^-agjE~pIz@}xj<78x(eW0mziHADg0r^0s~#pwm~D%!v+f%48+ z7okJR9JovR1i+gJ^fuTv>jL1aRahyM`I?*B2@t^ziW4V}I;_?vtKbGtE!U{)8LC?6 zpsHvEeFV(g<2EhZseAKv10hJvIwcmPfvRO1`gWRPLZChR*3qFZjPkKIox;1ECP+$% z3dyyJoKf_#jJ*IAKzr3d2tJE@A)r^$1e!DsoybZ)+u^j)7jz4tv8>|TVArAvWY?*1 zI(g(wH5JTK)jK8md741}>rl7`CbX!1xt4a&L3(@EJ^`rP)3bH)qdL!ow@2k2nK|_B zRBxF;r}?cFK?g$lNH29Byd_`{o*C|dP8B7=OS`WJRO^9obYZ#=g!ce?6+K`jBsI!y zq^V?~2flxdqrV=|O$2%y>{|4I;qBoIbV95^p;2`S&`+h$@f=s}x>ZyKC&O+v|aE@;TdR3YO|WYS1jM$C+NZ`A8XS`3ofS% z&>-~EaPJ~ZUzt{1)ieDNg(h{B-|Ma<-UH}$bOC7}L8X{b#8Eu1p&megUCc zEU7#o@amp19XfE*9#`<8oxC@{XDmBS(RRVhsVrZTu0x-d#xA{fDle%Dx-iPd>aM$I zEI~3ZgfNi>I*uXtKXD{o`<}6MefEqyfnFz%lp9=nxGW>!ia8MaatAS$Y}{0T8|+$i zfp6b4PI8$mVkJo<1=JA6sH5t~w#h1WgH~(3;3K-CwkLvbrMf>skR<$~dsY)DSmHcf+WgYk)pNK~?9AEN zRcjgTky@srBUz%&b%pZJ-c|+0rQ|_HOw~SHE2z?Pl`3>3S5z3EN&$JBd>%O{T{}s( z##M5_wrJeEzcZJfHgBTFi3>mH*Kgu#>n{98MVG14ZALT?0w^*_-!zDZ)}m*q9EqiN zOk67_b+hCO*o2C=SsgleQH3ayGGcG2756Lxr}oW<4hti#@u-=o)QnF3S4wHKy2Wa| zS@OckHmLTP+Z~>80hFNd)o>k#g z6B7BoPV1@<(Du=_Bhk6^AxMX9~xm-_5&b=FLKjDA> M4za>wx^*8tLHhtc|*|&2v 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 literal 0 HcmV?d00001 diff --git a/resources/16x16/dry.svgz b/resources/16x16/dry.svgz new file mode 100644 index 0000000000000000000000000000000000000000..57cd45092513f55b8e8e82171e9937e10038fe3c GIT binary patch literal 3292 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 abpp-19sI+e8#^Gqd-Z=CD4}C?SpWc(AzZ=$ literal 0 HcmV?d00001 diff --git a/resources/16x16/emblem-favorite.svgz b/resources/16x16/emblem-favorite.svgz new file mode 100644 index 0000000000000000000000000000000000000000..9fdb44fc946d1f82cb0a9571aad9c1e63e38d0b7 GIT binary patch literal 3769 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 Y+^Vvt{&)ZJ;-^3Se{zx-DMx<+07a)yApigX literal 0 HcmV?d00001 diff --git a/resources/16x16/restore.svgz b/resources/16x16/restore.svgz new file mode 100644 index 0000000000000000000000000000000000000000..e5913fc3a16535a493674bd58e0143bcf373b397 GIT binary patch literal 10901 zcmV;GDr(gqiwFP!000000PTHSa~sK(=6nANEdA1o1&VpUuv?z+NMVm7+A$M5ws+qg z5KyErO%e+P@iqv2iI`|FDtq6@@!bSFb1ZxHDY=T%zrDX$&Ut^f?eOS1@9&1A^8P&8 zQ}z8N^j6W$7|d>d4{httj@jn=i%yl zc5a?vjGY7U@uPjYYB+N?zTCPt6X!wi~R^4-JiI?Nc4&dclaro5fspYwvBpZyW@e;${Y_w>Dr8t4@%@={>yJOXzpAM#7=z=!n`hI_Sp3HE2NnaeL>nk-?p-M0jDA6Q;oJGXxc zzVGh#Ph0NpS%k$Y!tTc6eJnFrCKsP)B@Bo^)bO1iAic3P!Z*ap{rJxV>ceP|PI}?- zHw@iEbL+Hymk=GdBB45KRv#}nE?=gXi5?>s&;^)ME@JSr3t)oFR|qiY3g@_$3D~&c zg;MbYZ$R%rxx52jM=8lu-dtH zSbIWZG>WNRU{S0s(a^@ZGldu0n`HCOg8_N#OtN{QMB%iuF4?^2&@jB?yQpocQh_7( zK>^Niri>(RDSRQl7Zy5|DnLZ`cyh!|B$F~jj*=_GY>gbPmR{=|Icl70L>m!3DkV=s zk5Xvmx!%;2Ht10)y^S8bm!ZKYutSEb2zDgh20{YVO7c_}Hylc{6(*H+naP*g$q;rC zTiA|cI8nx}vG|WrO*?Lc(*f(8b>2avFPJdi8Pe#5zz5D<-vIkO*qmyr)Sr~nmDxq< zTGWZ{IHhGxl2BP(!3}g6{8=bzz#t;+Wj`ksI8Z5UB*loKXlMpb@yHMf3`Lm2=#-ag z3%=`x6sJ{eyxsv}fd@N)mkJ{a<&3t;)}_FPnAzAmcaXizs&w4wdd7-ce&tzq-LV9FaUcA@QBZ}axuX`#f#E+Q21fnul1 z7T!2zQ@n>}08)YtknCUiLhy7hMx8DcWKnC~cPEsH1@;Hd7REvE6ek7QUcM#k4E$T> zThA|wwN#x!7-CX6=CERrqt<(73AS8$27`eHnb*QP40EfodkE}4OjrEtHw<3mAZ8hN zYce2>0CNHGSe?Jj&b`detrUViz{Vsc3uToxsS`r;Lc>Uplsj4*t1u;8q)7+!T#+kK zag*%H+CockJl+t=cB(Akdz@+0CxeVZNeM8w(lG5+&|UCQ#u&h#7f_3w)P2x2RCK0Y zQ;lT@-tdTR??6AmE3(YC;qmH7M%REt#Yy_rD&zMkNpi`Cc1!~4&}*=+cftVW}i!|_5DTrY=LPZ+8F0M_4n18>2VxZ`~R z`J$xNy$?O`I^dq+T&zYp2DvR@tT5S^P@eG>YcMEup<&>QzJw%p0%Qb}ci&d5hDt*aKp_c#q= z(*n3m4@M@YlRg^6eGS>+S^&M*!?>>jF))b}if^Imaq;XrUTi(Mez0HS;q^8?gkkQT zHF~vWfyJPQfT>v>1IKs_I>vYkNQ*_ZfRV8RI!*$V$(&H$E{{j~+(G`LoEf{q>BG_iT&yTG zhaTelWLkl5IS~dG7`s#i73LXCp>VjcipYqz(eQ`GFd!gSw+LA03*#9fO|=$17Uv4* z;gw1s3wj`IGtkI}y0Wmy>DvRQ3aW}))ihDCf=F%cULuX3$!r{i+|bXS6xjP6I_ z=YH{DWHv9k&hJZrcn=BAmF-x5$Y+-yQK;iE`OyShvN%`g(op6J*h;5t+S9pfP-n!Z z1%s-Dld>_J79573Wx}M~1?&sMR=JoH2?65LUdLRT#tb7jTEeB*!wHvO4@Zns2=e18 zYhxBGAX6m6Z_1^)1}89JoGF*)3Mx2ulrnmS$2D?8B&-G#F1;R3xHJu8n<{3~v=-!n z;)w*)rqF<*+K_gTD#BPHVkXT&lwi?#*oaAI&M|Q5^?1Uj$&QscHy^WU4z3E#ncFp+ z<_2=cI`FfUAHv+11}1aFr#UQUuoX;=_%sdDw`|I%1;Z{abmf#!hhb2-iTSjE$^ZtT zV>S&7w-5lb+juN+UnO9jCT!Y6D%waO$vjkIHoad%lW$N%JFNh*P|P$Jv8Y%JkSKs2 ztSSja<2DLh94tU!)-l}P>Hbnw1n;RacyMnj?ud1-jZkW)@DlZJxGH*5`$OmA`+)aN$e{iZL;HQGH6MIF8SW)B(s zQ3+@Uk@r1#wjjrxWLhOaAx+&YXvBQyepMrNL5fJdJLa?@+87iDooooQKw5w_4OUxH z{*{j?t#WSaBAvU_>Bb5HS`kpfI2fpAQ2h>}ks34_K`r&d9RW3EAZ@`Cd;&IzA_0Sk zrB3dkOG0J03E1j$_kj(3#Sqv!0T+K(a5)Mkd8ao5#?UgLRGHMRI1r}r(wHdB5LilE z?_I`w?;_E0>TS2+C5~)YwGmB+?Fi#j$~5?_DQd?cc~TOD0q#vK?CP60#<515?~;+` zQMoVMqHu5`Ll1*6a4DyPpk>l+x~4fPXR-AdFsL>)2PM@|+(c~p^e93)4s0uxhDxBW zO5x*he%O1o!N6#0vvS*xq{+^0M~versRL_8!u;NdeaV7Mb(QQ#kp9KJPlYLoEwAFy zBhs9pt#ASn#lH>rF!c^H{5QEs6vKKBCj0w56F_c!a|g}KK_&>=(K1WsBF$n=}?*7)JEt~o#z|ewCSPdz^b2*{+n-pAkuA% zlry+gxtBYIrQ!{#k+o4Nw)+YM?0>JK%)5~L%OvSUX`mESLAFw-xx}@pqwLfa&+dlf z23`UfOq8CXVfq)|K~!sA4kV$mb+C>Nec*U}!zn%);ss6^&ifQSp^Piufvdn9-wfrF z%x%WV6OA%NDDCvC#m(7d7x`GmEn){yHM9jK*%@C6|QE_ zGCPA(oNBx6xmE7D_v6xdJURqQ${vDCCVSSj?(&1gf)RK35{bq=M1S!Z9uPf()|Vx2yr|0F z%7N&M;BOJi(}#8Gn~=I-7W4bM@QQmD*Sm$BL<(X;2+D-ojVY+)kfqdKpL>~|`wYas zv!Ds^?zWu#jT8*;<$^W_of0}0y!hA*=Z7$76Ic-+PuV2jw94n`;uw`4Dpu` zc@7vRd4%mG_`EJG)10MQ>Gipn*|{&?&x`TyOsn5LC|{(P+&2kI(Y>VwMT0BmgQXRq zsXR1~X9woLoH(U=}LAS*!T);{96WF*VrZ^u-R`HY@IrLem6vF+lKpENVpS2>)F*+p=1Zp zYOncAQy8u`TW0`v)MhG*C}-zhX6L@RbC!uzSv9ioG7+^^^`xdOFq`_w$TtC%o~PLg z^|{A{v<_{x8s*op7_S!%ktvfvXB#4O`dM$NO1!bgfj}Y}UFX+uEsbRUB>6Smd#-Gp z%9%;8iQChkyw&PnL(;!UaO7JwBa?D)9&AJM(#Otw3PV!EA|X^-3r~J(XZ?mRF~rrR zwOz;jLLL+sj%wJc7U~5%IPxw$yqlY&bR|GpT8>>>@JAk3e6>CsQ!pX5O#S2FrJP#F zyl7-0_ui2%pvOHJ=yvE)MUXXIr_ciqYaC~k=Nh1Aspu=_LZK6YE4)+OpEhfTiL_}% z&533cu4Gbnq@Z~TtE0TG%XHFZcG4^)6$orE(^b9>c2Owk9@5#+MOqmtL9>Zg}d zz$#s#ATfMj0Jjz|A@~MNlYPGAw4V_*hl_(rb0zp*-dc%LNsd<#Jo%XH9Daq4z ztKRPqZEFRe2#Q-gH;NaKG{#dJ#*2cf11=ZsODJ0?7&`Sdq~js`qK>-`V*J_T=ie5X zx|Eqvyo7{e#$-r??$d}OJ1U@8p@cTDAl?%E_8Dl*b{vfeXr{H;q(zlqtU*tlAHzj-w7sFuf%p?Ik^TBGOBNL@mE1 zjB&n6cn0>fbB9shhW9sm%~f>A{lKfdxmpur6}9q8#kKKKCd|q!%sF$K=e)nnNlvec z+3)?$yd%q}Wv{94S6ItMT)WbwNl26G$~5IS!P3ui$K07k}=S& zI=mji1y)+Ma8_nTmg`LEIc@8wRI}LU`E{hJj~odXs*`ppvm8y#r8fC#!Xm zb1DpK3DMiqr?bh{J3Ix2$B&|>$N_*1vV!q5XUlN)xAI+DVZUwOofqsZQ#>feEnN>00LJ{D*{E-Ku5 z>*l?3wzT6&RjbhLL-~A;wzNpeu9okybWk^ye6h$rF5A9*Ghb-Zv4qLjxTMuA zJ8(z~1NM2mMBaj)jYQK)&@*b>@*L=yZxD1ade#+_HcCKq^sK8(CqU1f_LbM*atO*4 z5cD+slL>lxO$+=Rjp?@;{^c9>wZT7X72xFt{^b|T_vt$r{p!V=d%U;-j_uK}zRp4D zr@T>yUQ7Z$=wQk-mm*-j>h!6~4WZYiXuk{ngq8!@K@0HQt)aPk=~|%QCr-bO(9h=N z<;mEe%`w|!q;t)V*k=Ox)^+cB+h(U1&b!!?n>T~k+|eOAnhjmu*&>u7W_17q44`g!B6*%n5XmsZ3{YN2&>Lp|1dv8+tA0%n?&1!D{* z8ol(*L{98kcwIxh4DEeouC~EpXX!VKMf!MUkb1^P=N8}HWGSMMTFX{TsFHs3OXQoU z8tzrQCBioHOJwaipsQ`SgdEdveu;fXT#B+g@wiR%n!GEdEm@z~>61F3a=mc49GE5% zm~V;$ki)7I9+2(Q+!+s0Sq{@F572ow(2lTHfF-iYM{WWZNpAOqEm}tCIDmbbEa?5u`hLwM(`M-adS6+?Q{EH@zL0r6n7< zHes1o){XEdvYWGSJUAIj%8tF-X&=gcmJI>1OLW}Q;WFC|Gwpl1!wE^J>{h%gOz=F&s79@rDfXC?r{ zHX3jX7qxg!7q#fvMcq>RF5vjPk-mZtj6IEWF$9|$VEai_M*ggRZQik8yWSLje^}3V zinKdqbgeaga~aL}K*a9LZe?^MFZoG?I6vM%X(7bQ}}4Ev;bd;ra*czvBM zuaMX&#_8P^ix*aCNM`32uda}uyYT7?ZLETnH?q)>#IjEnL(WKRTS#zxbD-VvnNe@c zwZ^x+JOIb#{99Iy=a{z9Uw~_N6`Z)lDi!h0^k6I5XoH%f{$Dr-0MOkANkl>S{ZOff%*-?e-w3+R&Ze$z}>xDK*<@(OX& z;-;^bw>gw#8^8GI)8(bFzdJei+#SUHj2>~z_d6Z2Scf6M9FUZ510+#?8h&a9 zq~pC@mXDbnHA1Cq?!tk7hc{sXZ!j9Ex4w8i2Uziu%a2(==F=zInRoh6ANYs--SZY? zYde_z+3U!%OP0vuU3)v2G53MxNiv^`*WM0h%>HT164`vb1MT3@P@r46x?!PK21NWK z^v8vIv9p(~G{@IG?olE(CH@JNic|?WR;9?%c4P^k)~k1vMz-gzbI$;#=W>2lVA2Vg zs3)b6u`}sKpjEv*MTlkqYva85{o+*zG04};g6;7J3T@6yYCi3^)oyRBYBFa1{@qGL zox**b9TZGkwYlkJB)jQ04E3AY^z*XEH%sl^cyt|!1MQl6bXnWd!|ktOu)6TtCV`JC zt&T}$cKQdUYs2Y|MiIw~O(80D!IY2u0-*KL_?vp_N=}8zXh_uzEo012~**f z47RY+`_%nueW9IqerMIablot~*C`fT1=2fAmRZf~D!Z$m64T@*?mI!#4TG#N*3|9Y z8E6{5-@cxeoXzp=h8*gF-V;zoyWrmT)V?@ou@P*~f~cEN-pd9{Um#_&X|PnrSuDpm zRGOwv=Tn-y&j_fmoJQt%Ia;;jMpQY}#^OK9njVB4Y9o|iGAgU%DCD7YYr`M2_k0$&do#1ycB!An-wi8&lbaW!o0ET8P2n|X-`bA68gCK3WULK*7wk>49Xp7?fBS-6 zst)owIxKMS#2@pTi`#{5)p6>%eY4M9PPE@s3xFM82>bFr{x-cln)vtumTvTomS8zS zwNKOX>3j?;2O8T=F+|6#z8?O|5yQS{pm1$0v0aM55xxH2mXM>j5q;MM_Ui)Nbwf9& zPDQFG9c@WYYLbfNJE)3zbDK6DZTeMmNFz_4$^8vbI~3P$H2-6hkDnP@Ta%LavPbRP zD5AetyppLFRtsZ@=e%maovHHZ#hjxwdzFJxZc&|yJw%M-qmE0wX>>V5EVN` z=``*uj)qdx(rGNFP75T_s)29QDtuujI~qvM8w6UJJCg1S%;OhVwC#Mi!uKujr#Nd3 z13-K$7^BtlHTQ==yely3Cb7U$zw zX&uS_jw=9K+nD6ZaHR_XWM0;j*)(^pyJUq;!-9Y^oyKvr10to)XbNMZfEW>u_5~Bv zbZ~=y(wRg6VHDJ9k3%A7;0*6;hJ%phJ^uRTwZ3MAv8hDC~46}Ya=6`@C+ z_ZajiqNLc}iHK+dDXD@h21Ovn_+{e0&pi>mLpln9iRNC7}r?^ZfU1lc*1+Tf; zCaWL9G_m~jt|9nOY>@57k=8GzJ{8-(oLtY#S>sL9W~#498wv2o5WHea3a(5P&!~c0 zObW$efUa5f*U5PP8R$?jSdk*S@VT5^eV&IaMmZtY-nYy0FT;oX`^oV3->)B558U99 z$s;!YSbmud=acCzMOHWikutv7k3U^OCVb?)+VqPW9@f7a!{hnQS?jx#QLogTJpoXY zGOjvM9otMCAi*NFN_Pm!?X-@HqAs&@FSBzSa&%B7y$eiOaCN7_EQBrxmJ|psI1y_( zFcHJ&8u$xgzvlb4HQ#>$c=PUT9#&|@wA7Bgm*Bb5n+3oOW6~-503pD*w*aauptpD- z3?@KDb6yKo!)=^rTt*SRoYc%65_;3wxjFYTJ2%De!$pk~QFXvVq`)+iI*KVRM2*l~ z2@~m0Yf)n?Q;~Nv5;d9$r{!s6jiJH{7l(*4vc^f4l{G(~kIT#Z@YnrxJh{XFE^dc6 z<@n2_{QAS{Zl8uLl(`#Tl_BZ_}v=xMbhOIQfH7p-PuP)9#ExC+GM_z^@z)-3%XE1CJ-Rmd^lAE-zy5V{i&Gh_ zHoJOh(&mii-i@yv!eYkbI;y#OiLgf3&WE0TCuRg>{alWIEoYym!`V2q05<;X5^2Yr zez!cr&G2z@Gx@CyH(5|p0^KD6&Y$ke_h*k!G?p+M8W1|Ng>%Qg8huJfb4T9mO=pv< z$?ant4z7lt;-NMARy^S6^E$DI@tbl!91rKiHRUfygyG9xTYX(FUhM%SnIPDeB!@Tk6f+Wmv{>RA;4(9y+%hmsWytxJsT3tvc6Cl?&t!_Ij z@23y55di#oKEJy-KfimJT^G~Y6>ft6ZYK1l^Z%YtuCM<}i^LJ{^TmU%a+22j`1yRf z(eUYN0|l;!pUUg^XTMCol-JkOUJXUAX48keo9Vb*uz<5Q02eG_vF25R4{KR5uUnu6 zq%7pDTj-EhajtxL9}@SHTW_JRkdm$ZyR~4s8_qvRvSM}0cr*eGkYF^rAFY2jqy~Lm z#4l^=^)ULk9_M5PTBG_x;1A<{52l{uTHuL1E0mG0H|pWpEYrf!tD3x%QuUxMSO`BO zpekt1Jw^o7FCn;Dk3heG7NJ$2(N!3DUWI*X<4iPvu>8U=J*XI5T5AP84=g|Bdmnnf z(9A*w(tS=HK#a5);?zEq7K2j4wN*W?a7{8N)H_icb5AVpLO{fsdd?+)fz>rE{l29NO)cmOz4Euu0cOXA?){eN1tBW~qW$^;B=KX- z{9*pxPjod(2MHf%57*^I`K1Jo(A#@w(_c&A%Wb*%yW;E@(C-A7ZaGLd2j*V@?{CNP z;Qv6vq@y6oa&`^YIloZLvGEWrdo~+BB`d8SEGF6xFk0#)Tk1PTzbTAf+mUFrU{p81__q?4K&rp?a`|@g!Yj8VB;a=|SJt zhtiXG+g^< zj~m>P9I}On+72ulNPNwC0b^YkTn8$(SRWW2ZueV`CkPw!gu48t*aH2>Kvg!-k`D## zL`mG50CcRQ*Uu&feC!lETw+~|lyG}n{_o)83A52sF z46-JIt_YHV5h#=(9Vl`^&Y;oWAW-rS%Hlt5MK&#tLbhimsT^)9CEw+bc{ZI>Q=@@r zeGOhI3|Lg(0J_dO;dh*HjXpQH5ct`f0oiw{_~xYIiovMlU0n?nqy0QfdR}AX;z&l$ z6GqNDW8^jMz%s?fc1|X}eAQwzD}fj=fg9|rPl71C3YRkjacCVd7CU~o7X@}7eVri! zcZHE2N2Zyi{wvho>bE`tz*%zMHAkvb~5az`jD zOxQO=n%|X`x2vqM#2RX+tX#D~XI?i~wzC^^61;6-RZ=1v^UmmCUHU?L$4nnbxil|R z2wq8QW*qs(QySkT`8Qo+vHoaPa-kAyj_{d5kWqglqlgj=Mb?zJeI3x+3R@|H(uLSC zcA}LC1OvUn_MtQg&VjYWBALLg6nSp&C(($jb_}dGr2Q#O*HCIy&*3j?rI7fG0pq;~ zclO*0U?SM*KYLi6G*4+j8r%_42&jvKI)GMcB=wbH&Pq}v!33NQebuZYLIlxZ3&3^| zjiQX8ltJ&sETQCa>f>o}ZKqr5{AwQwgKRI`<{;LK0DNgA!HfG-qId5>0h(^HbV@58!L3*+Vsv2SA8 zMdd*twHm0x=r8E!yqX#^w}Dg+>NFIjU@Xzg&nmZrhVlYk+>nn!K}PZ()D6f>&2ghb zhjpM0DoH7Ki_cU;$ho60^^~S|r51)W7B!V_W>i7*`U79M$|#e;D1!=5_X#$EXHtgm z2&itp$Dr;Cs9Ae8m-i!Et*iprdT4wBks5E z+Rl}rmnU#=L256$gQ=|_P-hs`z}v;aO0z!MO1q@Z_I~SOk^rE2aQKLdXIUehsdQ)! zyNK*#9E1#@3vliWK$%V%h-_u(jZK}g!Rj#Lp~HufYRd8}s{tsfiv`!uV3v=-77w^Q zlFG;f`hX*{a$s>g$EAibhZQYnO!g#ostrd8^umw##J_+<9?Uc}ay7a_A{Q`#TwRL~ zI?vylkh^tnlHB)-!5RX$l%)3+5#*&QOB&}akyMj3g}G-FEM>v@0#v{s!I4PFvKSllFD!9av9 zXuxkYC(jL(5LjG>EF}rkiIb32L>0k!f`A3jsPRHFpb+=pQ3qSR?Xe+oyMc{WCn zBtGnO-&rS`L-n*yp9rRG7Z2Jf3|f>1#X@oR80UxMGbXtBNE)4kHinZnyEq2{m6qH` z;2Th39GwHDLTkgfoI}!rYo8axdh;gEA?qu5?%j#crxy7E$J@>-z=qxgF!N|5cN({c z@`vZ5nkG*twsWueH932aykgVO2pl^H!_=@;XH+ zg!`;A+A+teHu0}X?ifHxOBC})Q77m=%0NF)c>##`{yeh#dYZw_u^a6{@s5fDc z-x^NW0|&1iryB|P!Z4!1iE)0e0SIU*?S> zOBA;$mO!#w-TmwL`^>W_O4^!>pGtuA3wc(|N7;-$M+8}p1ps3_OIW5_09jdUXMS1czF5g z;q~$J+fP3n|Nj0@A78!v>EZaNA3lBh>G{`R|NQgMx3~9$x5p1}zW(Rqdj0mRufF;C zmp5M>$CuAc&phz<^^0eGw?F*+_VuS9UOXH9jE_G&ynXY-rx(xWsJG6~ilcvd`|$Js zeEjQ+XXkMqoO=BF>yNy}PyGM<_2aAeFMnk5k2fCh_x-f{(w={@j(Ysl!+2G`fA}t) z=jF&}uGe&4&-Kr736^^Q@%ht*FP{CQ7xh^me|q`q;p5j&$G^D#X;q)Ee>!mc`1t1! zuO7bV77w@g51+pNxBvXN&tG28+v`uSf4R-Cp8j)*=jQV~zvczMeAr*!{I_R*%cjc-zIw!3Z$JHQ$KU-4PyYG%!{g81`Qr~SUqAl*;#vOf34Zzc2Iuc@5X<`E z;nklWK79B1@&haX-6^jhKK|)xd9RcI{q~H%KR*8G-AgO2m9l>O{VSr7!?&7JZom7I zg|))P5^Iq3`z^5|nKRDwl2iEz!2i(tK4Qc#??^r_pN8kPU@~>}yeEau@ z|DS1I{rSU(hxebZ?_U1x0rR~192#$abK;v1kAME@$H&(sMx6WG8@+q^-NU;V&;I>K z-0|#}DB_0S#;0$7dim*xeVo+&^tX51==-z6O{c=a!SxZWN8JpIeZPahut z^zi%-XZ+khZZkelKmGOTz`M8aA9()r@BaMh)0fBo@8jF|&wqUT>EXlE(a*Pj_OuF? z_a8@@=OvxTACGd5+vuydOV78tPJTafnYUVMnwONut*lkoG5C%65| z+q|^OZTVS8o9lMYQU?n>Z=*9sCEw134Z;?clhaXKx{Z-$ zzp%2pYF^{Xp#SyR%!Yx^Z=a9+0z1Uhr@#I50N%a&^5-6e|6SCyGrvy#OJDwYl#VTX zJKJTBd}FO$&s_6u&6P9nLw=9mj_bkzal}~l?x^yNpp-D+;$qyWB6Xr zdgNYi*u0I)SQC5X(jk(0TP<_mm>G}P(T_CnP-!_`#whseOf%wP0TlBxy4Q}*dFOen zZQ%kIc58LCc8v43QXR$f3K6h+Ik-t$gGJxZ!)?6oE$0XwTtEp_zMH9s_`}!KGH-vbT|slnKgqlC)*%E9{0z1E)v6l$vkic|K<4B{NI7 zl(WS9Rx%Jrr;*krPjaJVmcj+3QD=F%;jFx7CLeEk@D}Bgap2UG^EOiHyWB zDXOpK&`uwjR%&C$=YGx$}zK zaSu#{Q-?7JV2-$$^Lw7;r!ZW6S3%({o_Q3lJ4(G^i8)T5bJUJo>q&f-TV|mkY2tAN z{2~>rP9@**14b_uPZO=;DDed%nz9%C0=%z7le7f#cjF1%03RpO0vy~{>3E5ZSOo4x z!0<_h+-pOFky&^OCunv5ArEb*9u`wo^fr9W$=cO z1Cd6aU{GT7JiENY$x_I5fH94A)pg$4Xb@LE_QVF*1QbsoFZacO`k(`8L?19hL?w?T z!lDvmybr5s5Ie_)vZ9ZE!9uLO!hQe*(xIYbP$j96nd*tMPJ#f35|$13L11tiNZ6#! zURflWEN`+*vCAcT!36m(ei$+MSd8Hz%!wF1zUM+ua{Fk zoX6jf9}nO&^*}jD4P+`{Uo)-2WuT)8k4xmh6w3_04zw8*10t+2hB&v9`3hul8tZ}S zh${pNKI9hU=&V8igh&sSG-MtXm9KJ=-9#OjXL3UnxI>^J5s6SjwGl>wE+kdVur?!e z*GfD9^LKFE3kawVdqrB}e4Ya|;BsCd6McY%Fzx*cbOt+~gaa1>4}Erk)I()?mXHeX z%NA!&8{uHDVtO?EJUoJ8AZo8fLYpcGR{%j(leQxEQm}Z~5OZIv-)0CZ?v8Zp5m*%y zwt!@orD|0O5o5{kG|aIYIq0F0tKy4P zDL{lTKrPNmAjM|MfsLRSZcSqNZqgH(9&e;%Lapbcp9Z2gH$9eVk-X>dta=j#rgT`+ zDI3a=BvC%pVLeGYvcM^%fe@@NxNj2V$s3`;$p{4@%LLJg>~_lLggM!GrdW9u+YF5+ zXqhC0`1EDSLBUH(LhR(YO!>7u2|(4)BzWqk27v-xrefpDrn=20Y|>P?e4=~8=o3blj&J&mCn5!T)8O`2j0G=4)EbZ290Q~3 z`ia|Gh1rM~qD(Gs)<=!J53J>hlsYgN7$D`sOGh1X29z;{d6g*u4Y*-+C=oOt8%(K5 zU<1%|Xb*%{XwY>Ue7+1n&6O5}Nsn|zSc*irE2-wbIE0m#dhzrElt(I;dh_xmbBhUb z`DVDQIKpWftZ%& zKnJiJo2*Q^NA(Dbn*UP`0H9LvvI1Twi+gA*RGR5nNNJ_lgmPn*Et6_-q=s4Yxf8#5 zPxDz{v6w_ja#>b#-LOO$m8@HE80fGKIjwYMwW7K~FNI4p<<7Lg#mwk=I4sfyqShqT zMKx?mo|Q28>fz6>&__JTtzfnH!Vo8~BAH?MsU{*dkdt_BK#N0sAVwes^+uO}!*4tv zdRx9ew9R)=3YEnrqJN3{OY$*Lc^wr*mK!n}GetBGN;rpGVgWb~%NocH?SX}B7JYjFD;E`LcZhP- zL-sMSwjqNMgk?d(&j%<5Dp#DA6}?6cQF;!Q-o~(ASfMpj@VNnfQmlYS)hL)LYR#mg ziK74{85Zux6|+Qn0gQG+H9{h}_KRWx@dqvi&a|~PKEE6an$yZf&__6_$H`T~H3ekP zwQu&`UGevz5aCH>(h-*Zt3~{?;$o!dn90} zSm^q#O(+&kliuq_q|-J9-vOU9gp-Ic(ms?Fj&>AtlS#-D= zYtViWBp1qaqD$~;@C7tHa@f7)n)Yi}>mzZAoR~RI%d3Z&Ak^Yd5K`aoc{qU z*<@K?c0efdHV67k)F+Lf8ev*2nh$kh>sv>mEW0Zn+%c+a9Gi*=YT9q>*QnsgPjnsCD zwnbzWxMafv~SyKLuH_NH#HoyKI8iSwWDvH=#_Du?}UdxD$a! zCfX>N<0;>b8VXX-(Y^&5XGG}@qSm;kMQIPdmx%Ivh@$lJC@IjLJWweGYkm8QTHlO;xeQHG}}LLF6pQ2rjlrBppCt0ii7m)ue5V zNrHxlrOO{-MwW0HmefE9i?_<=Ge;{G507ODS!%KF{4XpG$DlEmT_amfwd@G)BNU=Z zD%uQ})3w^_t_=}_7pOCpJj^z zX>DbGKayFtnz!xh>)~p5PHh49@p9m9`Q?CWin5HgD2i8bKWRXbEAz}XH$gyyAeB`n z#t11mIzsqjCM3%c)%_Y<@7ZJ(9c&z879nKvS|Jx z#OkV2>_u!#v6wYST{R``ZE{6+h-<8ZGvy1SYHC&3ev6Fzk$@(WSesiEhF_zVE^r8vSx|A&ML$2#IL}$TEBFh96EN9mN})J(e1Q z1yXsW7MEMfE_!Gx>>n-8}KntiOqbwz(D~;&mbY9u6sE0^w;|Dnk zWEk&q>QKK#tD5BFY%1cBC^ib&h!bt3L?dBXN_3NvkV0zl!gi)s1P+MWTR3aN7|kJ> zy_P~^UGCAeN=jhs1!<$#!a{Wak_Ti%#o1d6k+LeBxXP zPB z$)Y60HOj5bEmTl?uDnFI1dM>-3YwJc0*Acq7l)k9Eu;Jt&qhltG+l|Lxu)oAr`@v; z9?^vu?@%*kaK|a1y{=FZm~hYL1~!2g)e-&PkGET8vwiz&&p1N$g;Z%R1_KCZ;zUnP zo3HWG-2!qb80>3iZ%6cI$J-N;YLy?OE>5vNs?9d;6b-gH z0}MeW5D0t3e3%Ssn=rupY&2;}3a3DP!xoi?Dv2(?^-_wq91K?u^{SL7e!@W2^_3=N z+7ct6jX(QRs!N4`kGee1Vkv011xUrZt*shrCtJJ8oH#i2Wn}N6V@eJ$g8&NgYg z1CAMGZ3Dk1alC8z)8=))H{FQ)5mha56kU?eB5gf}649e2MT=JFD8+0oG<{)&@zV8P zst5Wt^{{&&WJOS!U4LI#A*qs!G=Ov#GYrwcP`HJSTMqU#cTwH>0KRjr@M9-ExIo2LU!Ck*j|X! zpP4r9uMv)KEye}aPU=4Lt2hn;bD|$wka~xSOO&tubR^<95#cg)94f;eMpdDefKgsA zSr*x_Ly!g8DQWh-iiN+gOnJT=(zXnPQ_nMo6Z@B)IQrI_BR0Gk{*g6mk^q>~sg@Ir zBlP_l%|Y8Z?T%vrTR$CTMP;o@ugY}zVR)d!!9mkDz*oGqCDrALjAMU7b0#bai{nNHI!+ z>ET@WMr2z^(O@Xi7~uU%#8~TReR9CqLW#v(qGP}I#3MPk_XzIW(uuV=^s(BD#vsYi z3?wM@QpAwc-9wQKNaZs`7180ODUf3XrX#m>sA6%3ri$XEtmQV=IDs z5wSB0NFS*ZUZl?)yTgda68{}g#%yjk!gB0x!2&H5VstxIKTf`^KFtWEL)*k*9xdr; z25#jOH5TQH@-GH2+%N=iBMKa%MmMQ+H@dL!)}m#YemT;k|tQR}iFOIAj3Cz19Qr1VC`iurk8xEREym4N!XI#;`hH8V#YcYK)f2>Si^A;Xs~feU5;CxM_~S<~GolesF6S7yL6H!RG9HNN5gLf+5%LRMG>?K**0}E*5Y%@g%v}M^DlT|z zwH@EXQAjjNxl8LYIIXc42Xb}W$f3Sjtw-pld(+#85K=;u#e>LpZ>?v^G`4$0$&hW4 zWpvxM>ks0dn9IOL30_8>4x*5kn9XOdGd!$7ED<tX{ zz)`TY70Ct0VRbz;uw{QxEv&Ew)*ALl-GdN)LMvzof4!=~LZ!Po$SU5NJ4&`|?D9fA zU0n5;p(gMP??$qi8dUUq$G~(OpAEyTxNE1KaaHY}<2%`BJ?`)6ImQ#k2_bppwh{DJ zG{c*Uq6AFlvS;umMn%Upc6qhfkz|P=Kx2P@v7NGPRn_{uQ+q;e}FSsl}rty zhqhI$bz(xDP?eY79gyI?oGzv_Cfsf_U;qGMmb2$ZXbDV97C~J%eh| znsYaenyuwxVggA$>M!a%OB{C&i-=dV0dV8aBWAuK7|xMej3cY%@9sE^9=p)ob1R(m z{Q{8o}OZOfbw+%le!8RUbcLimAQq(bk8;Jmr*PhARS#^-9T`) zxrSoGY<7I+i~&j;X&Z@}^B(4@9sm0PIH5WFCdw!G>^gVgP>vQ!4s3}*REp}c=XhWJ zk=^`Y(c5A^_oNL&2jRaK+d3i-YHW2Hd!L6hZ%NNVw!vX#Yv%+_P>i0_t%R`^WCR%9 z)Ppfg=+1MF6xl~jgAC2~acfk&6>-~QOtVU82E}a?RbvaLRUP49V|)!88YNgWvJMS+4~*!}kP^!56hufCxIrzaFwyDm_~Gt7L{{J9 z)z#f6=*FWEQ9RR4V+4owL%DZXo(8y7)Aw!55i};r{%6bYGj9l6C3e-Ry;ihR43$y0 z0)cs*z}n-;p_37yBRW)Wp^gzi=pqdTbLidGf4 z-}a=S^bl`#kKR^HbDB8T0(!a~nrpR#F;m+kw@`BJyf<_O)D|5r%R)JOmAXTm#0rxu zJc4I97uZmvxlz~7wh1(CDl5Y7m!_D5MPc1kq`?x!jlR)bv~>}k%DG}M{T|tdv`lWV zS9!D)8XNqaZiqW~XV~p!wqo5)z&V@cGTLYa);fxXXLk56-n_?#2j{--6;_*CGsbLw z*k6kticN>4$GTl0jHo1&6q2#;j!5bPGjx&5QE9imLoHY;oInI=ch!kI>+I>|xp(t| zj)K0l+V)_%ipm$e7RzcbhveH`JiGEUfs>?v8H)S0Tw zQTOz@?7td|QPr5Srw<(HX=gxL(Pf_!wemUdYA|%K@)_(+%OQjRO7dsci$Xm~6AjMT zhGB1II8~M)pgsL+vrWc`9UXmM91iCgW_CNnIQ5G|-mutq4wqO*M@`xsCaQyW0cKCz z;bh3_WHSw67-Nkeb2DL@EmuM-*FL9*A zSW($>miRM@Gx{JRu{%KR()Di5?PS!-aM^gvahFc**0m`J*J5hN(rv(0w;F^=OAGYE zkWHu^?lHC~i}77Z9LFP~8mGF`VZ^SBy&%Z!5Xf(Ht>JsjrcLuO}F&Yc4u^}Su6d+<&K&`#l zUC*OQEdz(#iB^d{gQLXwUZpx`Odi(gdwSp8&?<(c^V|&*lF2RER0^jc9@QYz~{Yog+eAdkI1m?WlyLkNy- zoFhC~$CN|% H+&usQ5VFYM literal 0 HcmV?d00001 diff --git a/resources/16x16/system-shutdown.svgz b/resources/16x16/system-shutdown.svgz new file mode 100644 index 0000000000000000000000000000000000000000..b4abcc25b7f05998d1e8c14a85882fa246c6cc83 GIT binary patch literal 6070 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 w88jC*VZF1PTD!6Or+kTV6H`v4`7T+{Xh= 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 P?W_L>bg1CV7ry`ih9n&W literal 0 HcmV?d00001 diff --git a/resources/resources.qrc b/resources/resources.qrc index 74ace3905..7183bdb78 100644 --- a/resources/resources.qrc +++ b/resources/resources.qrc @@ -3,9 +3,15 @@ 16x16/carla.png 16x16/carla-control.png + 16x16/add-from-favorites.svgz + 16x16/add-jack.svgz 16x16/application-exit.svgz 16x16/arrow-right.svgz + 16x16/audio-volume-medium.svgz + 16x16/audio-volume-muted.svgz + 16x16/balance.svgz 16x16/bookmarks.svgz + 16x16/compact.svgz 16x16/configure.svgz 16x16/dialog-cancel.svgz 16x16/dialog-error.svgz @@ -16,9 +22,11 @@ 16x16/document-open.svgz 16x16/document-save.svgz 16x16/document-save-as.svgz + 16x16/dry.svgz 16x16/edit-clear.svgz 16x16/edit-delete.svgz 16x16/edit-rename.svgz + 16x16/emblem-favorite.svgz 16x16/list-add.svgz 16x16/list-remove.svgz 16x16/media-playback-pause.svgz @@ -27,8 +35,14 @@ 16x16/media-seek-backward.svgz 16x16/media-seek-forward.svgz 16x16/network-connect.svgz + 16x16/restore.svgz + 16x16/skin.svgz + 16x16/system-shutdown.svgz + 16x16/system-turnon.svgz 16x16/view-refresh.svgz + 16x16/view-refresh-purple.svgz 16x16/view-sort-ascending.svgz + 16x16/wet.svgz 16x16/window-close.svgz 16x16/zoom-fit-best.svgz 16x16/zoom-in.svgz diff --git a/resources/ui/carla_edit.ui b/resources/ui/carla_edit.ui index 321142a5e..8750c4dcb 100644 --- a/resources/ui/carla_edit.ui +++ b/resources/ui/carla_edit.ui @@ -108,17 +108,17 @@ - + - 34 - 34 + 32 + 48 - 34 - 34 + 32 + 48 @@ -130,17 +130,17 @@ - + - 34 - 34 + 32 + 48 - 34 - 34 + 32 + 48 @@ -152,148 +152,92 @@ - + + + + 26 + 40 + + - 16777215 - 42 + 26 + 40 - - 0 + + Qt::CustomContextMenu - - 0 + + Balance Left (0%) - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 26 - 26 - - - - - 26 - 26 - - - - Qt::CustomContextMenu - - - Balance Left (0%) - - - - - - - - 26 - 26 - - - - - 26 - 26 - - - - Qt::CustomContextMenu - - - Balance Right (0%) - - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 26 - 26 - - - - - 26 - 26 - - - - Qt::CustomContextMenu - - - Balance Right (0%) - - - - - - - - 0 + + + + 26 + 40 + + + + + 26 + 40 + - - - - Use Balance - - - true - - - - - - - Use Panning - - - - + + Qt::CustomContextMenu + + + Balance Right (0%) + + + + + + + + 32 + 48 + + + + + 32 + 48 + + + + Qt::CustomContextMenu + + + Left-Right (0%) + + + + + + + + 32 + 48 + + + + + 32 + 48 + + + + Qt::CustomContextMenu + + + Front-Rear (0%) + + @@ -310,6 +254,21 @@ + + + + + true + + + + ⚠ L, R are special mixing type. + + + Qt::AlignCenter + + + @@ -876,13 +835,6 @@ Plugin Name - - - ScalableDial - QDial -
widgets/scalabledial.h
-
-
diff --git a/resources/ui/carla_host.ui b/resources/ui/carla_host.ui index 4956f3e85..a68581649 100644 --- a/resources/ui/carla_host.ui +++ b/resources/ui/carla_host.ui @@ -474,6 +474,7 @@ + @@ -508,6 +509,7 @@ + @@ -548,6 +550,7 @@ &Settings + @@ -589,6 +592,7 @@ + @@ -600,6 +604,17 @@ + + + + + + + + + + + @@ -1111,6 +1126,30 @@ QAction::NoRole + + + + :/16x16/view-refresh-purple.svgz:/16x16/view-refresh-purple.svgz + + + &Reload (!) + + + Reload (!) + + + Reload file. CAUTION, non-saved changes will be LOST! + + + Ctrl+Shift+R + + + false + + + QAction::NoRole + + @@ -1220,6 +1259,10 @@ + + + :/16x16/system-turnon.svgz:/16x16/system-turnon.svgz + Enable @@ -1228,6 +1271,10 @@ + + + :/16x16/system-shutdown.svgz:/16x16/system-shutdown.svgz + Disable @@ -1236,6 +1283,10 @@ + + + :/16x16/dry.svgz:/16x16/dry.svgz + 0% Wet (Bypass) @@ -1244,6 +1295,10 @@ + + + :/16x16/wet.svgz:/16x16/wet.svgz + 100% Wet @@ -1252,6 +1307,10 @@ + + + :/16x16/audio-volume-muted.svgz:/16x16/audio-volume-muted.svgz + 0% Volume (Mute) @@ -1260,6 +1319,10 @@ + + + :/16x16/audio-volume-medium.svgz:/16x16/audio-volume-medium.svgz + 100% Volume @@ -1268,6 +1331,10 @@ + + + :/16x16/balance.svgz:/16x16/balance.svgz + Center Balance @@ -1435,6 +1502,17 @@ QAction::NoRole + + + true + + + Show Toolbar Text + + + QAction::NoRole + + @@ -1553,7 +1631,23 @@ QAction::NoRole + + + + :/16x16/skin.svgz:/16x16/skin.svgz + + + Change &Skin... + + + QAction::NoRole + + + + + :/16x16/compact.svgz:/16x16/compact.svgz + Compact Slots @@ -1562,6 +1656,10 @@ + + + :/16x16/restore.svgz:/16x16/restore.svgz + Expand Slots @@ -1597,7 +1695,7 @@ - :/16x16/list-add.svgz:/16x16/list-add.svgz + :/16x16/add-jack.svgz:/16x16/add-jack.svgz Add &JACK Application... diff --git a/resources/ui/carla_settings.ui b/resources/ui/carla_settings.ui index 89c3c77a7..b14dc2d43 100644 --- a/resources/ui/carla_settings.ui +++ b/resources/ui/carla_settings.ui @@ -511,6 +511,27 @@ + + + + + + Example: 'Tweak' is for all skins; extra 'skinnameTweak' overrides it for that skin only. + + + Skin tweaks: + + + + + + + Example: 'Tweak' is for all skins; extra 'skinnameTweak' overrides it for that skin only. + + + + + diff --git a/resources/ui/xycontroller.ui b/resources/ui/xycontroller.ui index 83c16a4b2..675d3bd1c 100644 --- a/resources/ui/xycontroller.ui +++ b/resources/ui/xycontroller.ui @@ -30,41 +30,117 @@ - - - -100 + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 48 + 0 + - - 100 + + + + + + + 48 + 58 + + + + + 48 + 58 + - + + + + 48 + 58 + + + + + 48 + 58 + + + + + + Qt::Vertical QSizePolicy::Fixed - + - 20 - 30 + 48 + 0 - - - -100 + + + + 48 + 58 + + + + + 48 + 58 + - - 100 + + + + + + + 48 + 58 + + + + + 48 + 58 + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 48 + 0 + + + + diff --git a/source/backend/CarlaBackend.h b/source/backend/CarlaBackend.h index 33d9a51c4..cafa55864 100644 --- a/source/backend/CarlaBackend.h +++ b/source/backend/CarlaBackend.h @@ -210,6 +210,11 @@ static constexpr const uint PLUGIN_HAS_CUSTOM_UI_USING_FILE_OPEN = 0x2000; */ static constexpr const uint PLUGIN_NEEDS_MAIN_THREAD_IDLE = 0x4000; +/*! + * Plugin can use internal Front-Rear balance for Quadro (or > 2 channels in General). + */ +static constexpr const uint PLUGIN_CAN_FORTH = 0x8000; + /** @} */ /* ------------------------------------------------------------------------------------------------------------ @@ -833,12 +838,19 @@ typedef enum { * Range -1...15 (-1 = off). */ PARAMETER_CTRL_CHANNEL = -8, + + /*! + * Experimental Front-Rear parameter for Quadro (or > 2 channels in General). + * Range -1.0...1.0; default is 0.0. + */ + PARAMETER_FORTH = -9, + #endif /*! * Max value, defined only for convenience. */ - PARAMETER_MAX = -9 + PARAMETER_MAX = -10 } InternalParameterIndex; diff --git a/source/backend/CarlaHost.h b/source/backend/CarlaHost.h index 240ba0c29..bbd412df1 100644 --- a/source/backend/CarlaHost.h +++ b/source/backend/CarlaHost.h @@ -966,6 +966,13 @@ CARLA_API_EXPORT void carla_set_balance_right(CarlaHostHandle handle, uint plugi */ CARLA_API_EXPORT void carla_set_panning(CarlaHostHandle handle, uint pluginId, float value); +/*! + * Change a plugin's internal experimental front-rear (forth) value. + * @param pluginId Plugin + * @param value New value + */ +CARLA_API_EXPORT void carla_set_forth(CarlaHostHandle handle, uint pluginId, float value); + /*! * Change a plugin's internal control channel. * @param pluginId Plugin diff --git a/source/backend/CarlaPlugin.hpp b/source/backend/CarlaPlugin.hpp index 5ae7f50f7..48e1ae47f 100644 --- a/source/backend/CarlaPlugin.hpp +++ b/source/backend/CarlaPlugin.hpp @@ -542,6 +542,15 @@ public: */ void setPanning(float value, bool sendOsc, bool sendCallback) noexcept; + /*! + * Set the plugin's output forth (front-rear) value to @a value. + * @a value must be between -1.0 and 1.0. + * + * @param sendOsc Send message change over OSC + * @param sendCallback Send message change to registered callback + */ + void setForth(float value, bool sendOsc, bool sendCallback) noexcept; + /*! * Overloaded functions, to be called from within RT context only. */ @@ -550,6 +559,7 @@ public: void setBalanceLeftRT(float value, bool sendCallbackLater) noexcept; void setBalanceRightRT(float value, bool sendCallbackLater) noexcept; void setPanningRT(float value, bool sendCallbackLater) noexcept; + void setForthRT(float value, bool sendCallbackLater) noexcept; #endif // ! BUILD_BRIDGE_ALTERNATIVE_ARCH /*! diff --git a/source/backend/CarlaStandalone.cpp b/source/backend/CarlaStandalone.cpp index 6a96894e1..9dee9b705 100644 --- a/source/backend/CarlaStandalone.cpp +++ b/source/backend/CarlaStandalone.cpp @@ -2091,6 +2091,14 @@ void carla_set_panning(CarlaHostHandle handle, uint pluginId, float value) plugin->setPanning(value, true, false); } +void carla_set_forth(CarlaHostHandle handle, uint pluginId, float value) +{ + CARLA_SAFE_ASSERT_RETURN(handle->engine != nullptr,); + + if (const CarlaPluginPtr plugin = handle->engine->getPlugin(pluginId)) + plugin->setForth(value, true, false); +} + void carla_set_ctrl_channel(CarlaHostHandle handle, uint pluginId, int8_t channel) { CARLA_SAFE_ASSERT_RETURN(handle->engine != nullptr,); diff --git a/source/backend/engine/CarlaEngine.cpp b/source/backend/engine/CarlaEngine.cpp index 1731cd45e..ad4d61c92 100644 --- a/source/backend/engine/CarlaEngine.cpp +++ b/source/backend/engine/CarlaEngine.cpp @@ -2921,6 +2921,7 @@ bool CarlaEngine::loadProjectInternal(water::XmlDocument& xmlDoc, const bool alw plugin->setBalanceLeft(stateSave.balanceLeft, true, true); plugin->setBalanceRight(stateSave.balanceRight, true, true); plugin->setPanning(stateSave.panning, true, true); + plugin->setForth(stateSave.forth, true, true); plugin->setCtrlChannel(stateSave.ctrlChannel, true, true); plugin->setActive(stateSave.active, true, true); plugin->setEnabled(true); @@ -2976,6 +2977,7 @@ bool CarlaEngine::loadProjectInternal(water::XmlDocument& xmlDoc, const bool alw plugin->setBalanceLeft(stateSave.balanceLeft, true, true); plugin->setBalanceRight(stateSave.balanceRight, true, true); plugin->setPanning(stateSave.panning, true, true); + plugin->setForth(stateSave.forth, true, true); plugin->setCtrlChannel(stateSave.ctrlChannel, true, true); plugin->setActive(stateSave.active, true, true); plugin->setEnabled(true); diff --git a/source/backend/engine/CarlaEngineNative.cpp b/source/backend/engine/CarlaEngineNative.cpp index 0534b82a6..ec34896db 100644 --- a/source/backend/engine/CarlaEngineNative.cpp +++ b/source/backend/engine/CarlaEngineNative.cpp @@ -2115,6 +2115,17 @@ bool CarlaEngineNativeUI::msgReceived(const char* const msg) noexcept if (const CarlaPluginPtr plugin = fEngine->getPlugin(pluginId)) plugin->setPanning(value, true, false); } + else if (std::strcmp(msg, "set_forth") == 0) + { + uint32_t pluginId; + float value; + + CARLA_SAFE_ASSERT_RETURN(readNextLineAsUInt(pluginId), true); + CARLA_SAFE_ASSERT_RETURN(readNextLineAsFloat(value), true); + + if (const CarlaPluginPtr plugin = fEngine->getPlugin(pluginId)) + plugin->setForth(value, true, false); + } else if (std::strcmp(msg, "set_ctrl_channel") == 0) { uint32_t pluginId; diff --git a/source/backend/engine/CarlaEngineOsc.hpp b/source/backend/engine/CarlaEngineOsc.hpp index 4677f1ec3..d20a3b18b 100644 --- a/source/backend/engine/CarlaEngineOsc.hpp +++ b/source/backend/engine/CarlaEngineOsc.hpp @@ -137,6 +137,7 @@ private: int handleMsgSetBalanceLeft(CARLA_ENGINE_OSC_HANDLE_ARGS); int handleMsgSetBalanceRight(CARLA_ENGINE_OSC_HANDLE_ARGS); int handleMsgSetPanning(CARLA_ENGINE_OSC_HANDLE_ARGS); + int handleMsgSetForth(CARLA_ENGINE_OSC_HANDLE_ARGS); int handleMsgSetParameterValue(CARLA_ENGINE_OSC_HANDLE_ARGS); int handleMsgSetParameterMappedControlIndex(CARLA_ENGINE_OSC_HANDLE_ARGS); int handleMsgSetParameterMappedRange(CARLA_ENGINE_OSC_HANDLE_ARGS); diff --git a/source/backend/engine/CarlaEngineOscHandlers.cpp b/source/backend/engine/CarlaEngineOscHandlers.cpp index 1258c302c..3a98485fd 100644 --- a/source/backend/engine/CarlaEngineOscHandlers.cpp +++ b/source/backend/engine/CarlaEngineOscHandlers.cpp @@ -179,6 +179,8 @@ int CarlaEngineOsc::handleMessage(const bool isTCP, const char* const path, return handleMsgSetBalanceRight(plugin, argc, argv, types); if (std::strcmp(method, "set_panning") == 0) return handleMsgSetPanning(plugin, argc, argv, types); + if (std::strcmp(method, "set_forth") == 0) + return handleMsgSetForth(plugin, argc, argv, types); if (std::strcmp(method, "set_ctrl_channel") == 0) return 0; //handleMsgSetControlChannel(plugin, argc, argv, types); // TODO if (std::strcmp(method, "set_parameter_value") == 0) @@ -678,6 +680,17 @@ int CarlaEngineOsc::handleMsgSetPanning(CARLA_ENGINE_OSC_HANDLE_ARGS) return 0; } +int CarlaEngineOsc::handleMsgSetForth(CARLA_ENGINE_OSC_HANDLE_ARGS) +{ + carla_debug("CarlaEngineOsc::handleMsgSetForth()"); + CARLA_ENGINE_OSC_CHECK_OSC_TYPES(1, "f"); + + const float value = argv[0]->f; + + plugin->setForth(value, false, true); + return 0; +} + int CarlaEngineOsc::handleMsgSetParameterValue(CARLA_ENGINE_OSC_HANDLE_ARGS) { carla_debug("CarlaEngineOsc::handleMsgSetParameterValue()"); diff --git a/source/backend/engine/CarlaEngineOscSend.cpp b/source/backend/engine/CarlaEngineOscSend.cpp index 00207e99f..9fd1dd7c3 100644 --- a/source/backend/engine/CarlaEngineOscSend.cpp +++ b/source/backend/engine/CarlaEngineOscSend.cpp @@ -287,18 +287,18 @@ void CarlaEngineOsc::sendPluginInternalParameterValues(const CarlaPluginPtr& plu carla_debug("CarlaEngineOsc::sendPluginInternalParameterValues(%p)", plugin.get()); #ifdef CARLA_PROPER_CPP11_SUPPORT - static_assert(PARAMETER_ACTIVE == -2 && PARAMETER_MAX == -9, "Incorrect data"); + static_assert(PARAMETER_ACTIVE == -2 && PARAMETER_MAX == -10, "Incorrect data"); #endif - double iparams[7]; + double iparams[8]; - for (int32_t i = 0; i < 7; ++i) + for (int32_t i = 0; i < 8; ++i) iparams[i] = plugin->getInternalParameterValue(PARAMETER_ACTIVE - i); char targetPath[std::strlen(fControlDataTCP.path)+9]; std::strcpy(targetPath, fControlDataTCP.path); std::strcat(targetPath, "/iparams"); - try_lo_send(fControlDataTCP.target, targetPath, "ifffffff", + try_lo_send(fControlDataTCP.target, targetPath, "iffffffff", static_cast(plugin->getId()), iparams[0], // PARAMETER_ACTIVE iparams[1], // PARAMETER_DRYWET @@ -306,7 +306,8 @@ void CarlaEngineOsc::sendPluginInternalParameterValues(const CarlaPluginPtr& plu iparams[3], // PARAMETER_BALANCE_LEFT iparams[4], // PARAMETER_BALANCE_RIGHT iparams[5], // PARAMETER_PANNING - iparams[6] // PARAMETER_CTRL_CHANNEL + iparams[6], // PARAMETER_CTRL_CHANNEL + iparams[7] // PARAMETER_FORTH ); } diff --git a/source/backend/plugin/CarlaPlugin.cpp b/source/backend/plugin/CarlaPlugin.cpp index afc7d108b..592fb25d5 100644 --- a/source/backend/plugin/CarlaPlugin.cpp +++ b/source/backend/plugin/CarlaPlugin.cpp @@ -392,6 +392,8 @@ float CarlaPlugin::getInternalParameterValue(const int32_t parameterId) const no return pData->postProc.balanceRight; case PARAMETER_PANNING: return pData->postProc.panning; + case PARAMETER_FORTH: + return pData->postProc.forth; }; #endif CARLA_SAFE_ASSERT_RETURN(parameterId >= 0, 0.0f); @@ -540,6 +542,7 @@ const CarlaStateSave& CarlaPlugin::getStateSave(const bool callPrepareForSave) pData->stateSave.balanceLeft = pData->postProc.balanceLeft; pData->stateSave.balanceRight = pData->postProc.balanceRight; pData->stateSave.panning = pData->postProc.panning; + pData->stateSave.forth = pData->postProc.forth; pData->stateSave.ctrlChannel = pData->ctrlChannel; #endif @@ -953,6 +956,7 @@ void CarlaPlugin::loadStateSave(const CarlaStateSave& stateSave) setBalanceLeft(stateSave.balanceLeft, true, true); setBalanceRight(stateSave.balanceRight, true, true); setPanning(stateSave.panning, true, true); + setForth(stateSave.forth, true, true); setCtrlChannel(stateSave.ctrlChannel, true, true); setActive(stateSave.active, true, true); @@ -1550,6 +1554,31 @@ void CarlaPlugin::setPanning(const float value, const bool sendOsc, const bool s nullptr); } +void CarlaPlugin::setForth(const float value, const bool sendOsc, const bool sendCallback) noexcept +{ + if (pData->engineBridged) { + CARLA_SAFE_ASSERT_RETURN(!sendOsc && !sendCallback,); + } else { + CARLA_SAFE_ASSERT_RETURN(sendOsc || sendCallback,); // never call this from RT + } + CARLA_SAFE_ASSERT(value >= -1.0f && value <= 1.0f); + + const float fixedValue(carla_fixedValue(-1.0f, 1.0f, value)); + + if (carla_isEqual(pData->postProc.forth, fixedValue)) + return; + + pData->postProc.forth = fixedValue; + + pData->engine->callback(sendCallback, sendOsc, + ENGINE_CALLBACK_PARAMETER_VALUE_CHANGED, + pData->id, + PARAMETER_FORTH, + 0, 0, + fixedValue, + nullptr); +} + void CarlaPlugin::setDryWetRT(const float value, const bool sendCallbackLater) noexcept { CARLA_SAFE_ASSERT(value >= 0.0f && value <= 1.0f); @@ -1614,6 +1643,19 @@ void CarlaPlugin::setPanningRT(const float value, const bool sendCallbackLater) pData->postProc.panning = fixedValue; pData->postponeParameterChangeRtEvent(sendCallbackLater, PARAMETER_PANNING, fixedValue); } + +void CarlaPlugin::setForthRT(const float value, const bool sendCallbackLater) noexcept +{ + CARLA_SAFE_ASSERT(value >= -1.0f && value <= 1.0f); + + const float fixedValue(carla_fixedValue(-1.0f, 1.0f, value)); + + if (carla_isEqual(pData->postProc.forth, fixedValue)) + return; + + pData->postProc.forth = fixedValue; + pData->postponeParameterChangeRtEvent(sendCallbackLater, PARAMETER_FORTH, fixedValue); +} #endif // ! BUILD_BRIDGE_ALTERNATIVE_ARCH void CarlaPlugin::setCtrlChannel(const int8_t channel, const bool sendOsc, const bool sendCallback) noexcept @@ -1695,6 +1737,8 @@ void CarlaPlugin::setParameterValueByRealIndex(const int32_t rindex, const float return setBalanceRight(value, sendOsc, sendCallback); case PARAMETER_PANNING: return setPanning(value, sendOsc, sendCallback); + case PARAMETER_FORTH: + return setForth(value, sendOsc, sendCallback); } #endif CARLA_SAFE_ASSERT_RETURN(rindex >= 0,); diff --git a/source/backend/plugin/CarlaPluginFluidSynth.cpp b/source/backend/plugin/CarlaPluginFluidSynth.cpp index 2c8278bc0..95bfe3179 100644 --- a/source/backend/plugin/CarlaPluginFluidSynth.cpp +++ b/source/backend/plugin/CarlaPluginFluidSynth.cpp @@ -947,7 +947,7 @@ public: pData->hints |= PLUGIN_USES_MULTI_PROGS; if (! kUse16Outs) - pData->hints |= PLUGIN_CAN_BALANCE; + pData->hints |= PLUGIN_CAN_BALANCE | PLUGIN_CAN_PANNING; // extra plugin hints pData->extraHints = 0x0; @@ -1521,7 +1521,7 @@ public: { // note - balance not possible with kUse16Outs, so we can safely skip fAudioOutBuffers - const bool doVolume = (pData->hints & PLUGIN_CAN_VOLUME) != 0 && carla_isNotEqual(pData->postProc.volume, 1.0f); + const bool doVolume = (pData->hints & PLUGIN_CAN_VOLUME) != 0 && (carla_isNotEqual(pData->postProc.volume, 1.0f) || carla_isNotEqual(pData->postProc.panning, 0.0f)); const bool doBalance = (pData->hints & PLUGIN_CAN_BALANCE) != 0 && ! (carla_isEqual(pData->postProc.balanceLeft, -1.0f) && carla_isEqual(pData->postProc.balanceRight, 1.0f)); float* const oldBufLeft = pData->postProc.extraBuffer; @@ -1554,16 +1554,40 @@ public: } } + // Panning + // Only decrease of levels, but never increase, unlike 'L, R'. + // Note: no any pan processing for Mono. + + uint32_t q = pData->audioOut.count; + float pan = pData->postProc.panning; + float vol = pData->postProc.volume; + + // Pan: Stereo only. + if ((pan != 0.0) and (q == 2)) + { + // left channel(s) reduce when pan to right + if ((pan > 0) && (i == 0)) + { + vol = vol * (1.0 - pan); + } + + // right channel(s) reduce when pan to left + else if ((pan < 0) && (i == 1)) + { + vol = vol * (1.0 + pan); + } + } + // Volume if (kUse16Outs) { for (uint32_t k=0; k < frames; ++k) - outBuffer[i][k+timeOffset] = fAudio16Buffers[i][k] * pData->postProc.volume; + outBuffer[i][k+timeOffset] = fAudio16Buffers[i][k] * vol; } else if (doVolume) { for (uint32_t k=0; k < frames; ++k) - outBuffer[i][k+timeOffset] *= pData->postProc.volume; + outBuffer[i][k+timeOffset] *= vol; } } diff --git a/source/backend/plugin/CarlaPluginInternal.cpp b/source/backend/plugin/CarlaPluginInternal.cpp index 3f7bbd2e9..53307b9db 100644 --- a/source/backend/plugin/CarlaPluginInternal.cpp +++ b/source/backend/plugin/CarlaPluginInternal.cpp @@ -710,6 +710,7 @@ CarlaPlugin::ProtectedData::PostProc::PostProc() noexcept balanceLeft(-1.0f), balanceRight(1.0f), panning(0.0f), + forth(0.0f), extraBuffer(nullptr) {} #endif diff --git a/source/backend/plugin/CarlaPluginInternal.hpp b/source/backend/plugin/CarlaPluginInternal.hpp index 8a420caf3..eb576cafc 100644 --- a/source/backend/plugin/CarlaPluginInternal.hpp +++ b/source/backend/plugin/CarlaPluginInternal.hpp @@ -379,6 +379,7 @@ struct CarlaPlugin::ProtectedData { float balanceLeft; float balanceRight; float panning; + float forth; float* extraBuffer; PostProc() noexcept; diff --git a/source/backend/plugin/CarlaPluginLADSPADSSI.cpp b/source/backend/plugin/CarlaPluginLADSPADSSI.cpp index bddb071ab..f7bd8b1cb 100644 --- a/source/backend/plugin/CarlaPluginLADSPADSSI.cpp +++ b/source/backend/plugin/CarlaPluginLADSPADSSI.cpp @@ -1270,6 +1270,12 @@ public: if (aOuts >= 2 && aOuts % 2 == 0) pData->hints |= PLUGIN_CAN_BALANCE; + + if (aOuts >= 2) + pData->hints |= PLUGIN_CAN_PANNING; + + if (aOuts >= 3) + pData->hints |= PLUGIN_CAN_FORTH; #endif // extra plugin hints @@ -2082,7 +2088,11 @@ public: fAudioOutBuffers[i][k] = (fAudioOutBuffers[i][k] * pData->postProc.dryWet) + (bufValue * (1.0f - pData->postProc.dryWet)); } } + } + // Do not join this loop with loop above. + for (uint32_t i=0; i < pData->audioOut.count; ++i) + { // Balance if (doBalance) { @@ -2114,10 +2124,51 @@ public: } } + // Panning and Front-Rear ("Forth"). + // Only decrease of levels, but never increase, unlike 'L, R'. + // Note: no any pan/forth processing for Mono. + + uint32_t q = pData->audioOut.count; + float pan = pData->postProc.panning; + float forth = pData->postProc.forth; + float vol = pData->postProc.volume; + + // Pan: Stereo, 3 ch (extra rear/bass), or Quadro. + if ((pan != 0.0) and ((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); + } + } + + // Front-Rear: 3 ch (extra rear/bass), or Quadro. + if ((forth != 0.0) and ((q == 3) || (q == 4))) + { + // rear channel(s) reduce when moving forth to front + if ((forth > 0) && ((i == 2) || (i == 3))) + { + vol = vol * (1.0 - forth); + } + + // front channels reduce when moving back to rear + else if ((forth < 0) && ((i == 0) || (i == 1))) + { + vol = vol * (1.0 + forth); + } + } + // Volume (and buffer copy) { for (uint32_t k=0; k < frames; ++k) - audioOut[i][k+timeOffset] = fAudioOutBuffers[i][k] * pData->postProc.volume; + audioOut[i][k+timeOffset] = fAudioOutBuffers[i][k] * vol; } } diff --git a/source/backend/plugin/CarlaPluginLV2.cpp b/source/backend/plugin/CarlaPluginLV2.cpp index 6d84dc40f..90829ae1e 100644 --- a/source/backend/plugin/CarlaPluginLV2.cpp +++ b/source/backend/plugin/CarlaPluginLV2.cpp @@ -3338,6 +3338,12 @@ public: if (aOuts >= 2 && aOuts % 2 == 0) pData->hints |= PLUGIN_CAN_BALANCE; + if (aOuts >= 2) + pData->hints |= PLUGIN_CAN_PANNING; + + if (aOuts >= 3) + pData->hints |= PLUGIN_CAN_FORTH; + // extra plugin hints pData->extraHints = 0x0; @@ -4673,7 +4679,11 @@ public: fAudioOutBuffers[i][k] = (fAudioOutBuffers[i][k] * pData->postProc.dryWet) + (bufValue * (1.0f - pData->postProc.dryWet)); } } + } + // Do not join this loop with loop above. + for (uint32_t i=0; i < pData->audioOut.count; ++i) + { // Balance if (doBalance) { @@ -4705,10 +4715,51 @@ public: } } + // Panning and Front-Rear ("Forth"). + // Only decrease of levels, but never increase, unlike 'L, R'. + // Note: no any pan/forth processing for Mono. + + uint32_t q = pData->audioOut.count; + float pan = pData->postProc.panning; + float forth = pData->postProc.forth; + float vol = pData->postProc.volume; + + // Pan: Stereo, 3 ch (extra rear/bass), or Quadro. + if ((pan != 0.0) and ((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); + } + } + + // Front-Rear: 3 ch (extra rear/bass), or Quadro. + if ((forth != 0.0) and ((q == 3) || (q == 4))) + { + // rear channel(s) reduce when moving forth to front + if ((forth > 0) && ((i == 2) || (i == 3))) + { + vol = vol * (1.0 - forth); + } + + // front channels reduce when moving back to rear + else if ((forth < 0) && ((i == 0) || (i == 1))) + { + vol = vol * (1.0 + forth); + } + } + // Volume (and buffer copy) { for (uint32_t k=0; k < frames; ++k) - audioOut[i][k+timeOffset] = fAudioOutBuffers[i][k] * pData->postProc.volume; + audioOut[i][k+timeOffset] = fAudioOutBuffers[i][k] * vol; } } } // End of Post-processing diff --git a/source/backend/plugin/CarlaPluginNative.cpp b/source/backend/plugin/CarlaPluginNative.cpp index 7b8a56e2c..3651fa79b 100644 --- a/source/backend/plugin/CarlaPluginNative.cpp +++ b/source/backend/plugin/CarlaPluginNative.cpp @@ -1405,6 +1405,12 @@ public: if (aOuts >= 2 && aOuts % 2 == 0) pData->hints |= PLUGIN_CAN_BALANCE; + if (aOuts >= 2) + pData->hints |= PLUGIN_CAN_PANNING; + + if (aOuts >= 3) + pData->hints |= PLUGIN_CAN_FORTH; + // native plugin hints if (fDescriptor->hints & NATIVE_PLUGIN_IS_RTSAFE) pData->hints |= PLUGIN_IS_RTSAFE; @@ -2369,7 +2375,7 @@ public: float bufValue; float* const oldBufLeft = pData->postProc.extraBuffer; - for (; i < pData->audioOut.count; ++i) + for (uint32_t i=0; i < pData->audioOut.count; ++i) { // Dry/Wet if (doDryWet) @@ -2380,7 +2386,11 @@ public: fAudioAndCvOutBuffers[i][k] = (fAudioAndCvOutBuffers[i][k] * pData->postProc.dryWet) + (bufValue * (1.0f - pData->postProc.dryWet)); } } + } + // Do not join this loop with loop above. + for (uint32_t i=0; i < pData->audioOut.count; ++i) + { // Balance if (doBalance) { @@ -2412,10 +2422,51 @@ public: } } + // Panning and Front-Rear ("Forth"). + // Only decrease of levels, but never increase, unlike 'L, R'. + // Note: no any pan/forth processing for Mono. + + uint32_t q = pData->audioOut.count; + float pan = pData->postProc.panning; + float forth = pData->postProc.forth; + float vol = pData->postProc.volume; + + // Pan: Stereo, 3 ch (extra rear/bass), or Quadro. + if ((pan != 0.0) and ((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); + } + } + + // Front-Rear: 3 ch (extra rear/bass), or Quadro. + if ((forth != 0.0) and ((q == 3) || (q == 4))) + { + // rear channel(s) reduce when moving forth to front + if ((forth > 0) && ((i == 2) || (i == 3))) + { + vol = vol * (1.0 - forth); + } + + // front channels reduce when moving back to rear + else if ((forth < 0) && ((i == 0) || (i == 1))) + { + vol = vol * (1.0 + forth); + } + } + // Volume (and buffer copy) { for (uint32_t k=0; k < frames; ++k) - audioOut[i][k+timeOffset] = fAudioAndCvOutBuffers[i][k] * pData->postProc.volume; + audioOut[i][k+timeOffset] = fAudioAndCvOutBuffers[i][k] * vol; } } diff --git a/source/frontend/carla_backend.py b/source/frontend/carla_backend.py index 49c41f2ae..4d1a440f0 100644 --- a/source/frontend/carla_backend.py +++ b/source/frontend/carla_backend.py @@ -207,6 +207,9 @@ PLUGIN_USES_MULTI_PROGS = 0x400 # Plugin can make use of inline display API. PLUGIN_HAS_INLINE_DISPLAY = 0x800 +# Plugin can use internal control of Front-Rear balance for Quadro (or > 2 channels in General). +PLUGIN_CAN_FORTH = 0x8000 + # --------------------------------------------------------------------------------------------------------------------- # Plugin Options # Various plugin options. @@ -295,6 +298,16 @@ PARAMETER_CAN_BE_CV_CONTROLLED = 0x800 # @note only valid for parameter inputs. PARAMETER_IS_NOT_SAVED = 0x1000 +# Human readable labels for 24 decoded bits (currently for XRay tab of Edit dialog). +# Are some hints can exceed 2^24 ? +parameterHintsText = ( + "IS_BOOLEAN", "IS_INTEGER", "IS_LOGARITHMIC", "n/a", + "IS_ENABLED", "IS_AUTOMATABLE", "IS_READ_ONLY", "n/a", + "USES_SAMPLERATE", "USES_SCALEPOINTS", "USES_CUSTOM_TEXT", "CAN_BE_CV_CONTROLLED", + "IS_NOT_SAVED", "n/a", "n/a", "n/a", + "n/a", "n/a", "n/a", "n/a", + "n/a", "n/a", "n/a", "n/a", ) + # --------------------------------------------------------------------------------------------------------------------- # Mapped Parameter Flags # Various flags for parameter mappings. @@ -543,8 +556,12 @@ PARAMETER_PANNING = -7 # Range -1...15 (-1 = off). PARAMETER_CTRL_CHANNEL = -8 +# Experimental Front-Rear (Forth) parameter. +# Range -1.0...1.0; default is 0.0. +PARAMETER_FORTH = -9 + # Max value, defined only for convenience. -PARAMETER_MAX = -9 +PARAMETER_MAX = -10 # --------------------------------------------------------------------------------------------------------------------- # Special Mapped Control Index @@ -2102,6 +2119,13 @@ class CarlaHostMeta(): def set_panning(self, pluginId, value): raise NotImplementedError + # Change a plugin's internal front-rear (forth) value. + # @param pluginId Plugin + # @param value New value + @abstractmethod + def set_forth(self, pluginId, value): + raise NotImplementedError + # Change a plugin's internal control channel. # @param pluginId Plugin # @param channel New channel @@ -2520,6 +2544,9 @@ class CarlaHostNull(CarlaHostMeta): def set_panning(self, pluginId, value): return + def set_forth(self, pluginId, value): + return + def set_ctrl_channel(self, pluginId, channel): return @@ -2842,6 +2869,9 @@ class CarlaHostDLL(CarlaHostMeta): self.lib.carla_set_panning.argtypes = (c_void_p, c_uint, c_float) self.lib.carla_set_panning.restype = None + self.lib.carla_set_forth.argtypes = (c_void_p, c_uint, c_float) + self.lib.carla_set_forth.restype = None + self.lib.carla_set_ctrl_channel.argtypes = (c_void_p, c_uint, c_int8) self.lib.carla_set_ctrl_channel.restype = None @@ -3183,6 +3213,9 @@ class CarlaHostDLL(CarlaHostMeta): def set_panning(self, pluginId, value): self.lib.carla_set_panning(self.handle, pluginId, value) + def set_forth(self, pluginId, value): + self.lib.carla_set_forth(self.handle, pluginId, value) + def set_ctrl_channel(self, pluginId, channel): self.lib.carla_set_ctrl_channel(self.handle, pluginId, channel) @@ -3263,7 +3296,7 @@ class PluginStoreInfo(): def clear(self): self.pluginInfo = PyCarlaPluginInfo.copy() self.pluginRealName = "" - self.internalValues = [0.0, 1.0, 1.0, -1.0, 1.0, 0.0, -1.0] + self.internalValues = [0.0, 1.0, 1.0, -1.0, 1.0, 0.0, -1.0, 0.0] self.audioCountInfo = PyCarlaPortCountInfo.copy() self.midiCountInfo = PyCarlaPortCountInfo.copy() self.parameterCount = 0 @@ -3590,6 +3623,10 @@ class CarlaHostPlugin(CarlaHostMeta): self.sendMsg(["set_panning", pluginId, value]) self.fPluginsInfo[pluginId].internalValues[5] = value + def set_forth(self, pluginId, value): + self.sendMsg(["set_forth", pluginId, value]) + self.fPluginsInfo[pluginId].internalValues[7] = value + def set_ctrl_channel(self, pluginId, channel): self.sendMsg(["set_ctrl_channel", pluginId, channel]) self.fPluginsInfo[pluginId].internalValues[6] = float(channel) diff --git a/source/frontend/carla_backend_qtweb.py b/source/frontend/carla_backend_qtweb.py index c10f0e214..90a6888ee 100644 --- a/source/frontend/carla_backend_qtweb.py +++ b/source/frontend/carla_backend_qtweb.py @@ -468,6 +468,12 @@ class CarlaHostQtWeb(CarlaHostQtNull): 'value': value, }) + def set_forth(self, pluginId, value): + requests.get("{}/set_forth".format(self.baseurl), params={ + 'pluginId': pluginId, + 'value': value, + }) + def set_ctrl_channel(self, pluginId, channel): requests.get("{}/set_ctrl_channel".format(self.baseurl), params={ 'pluginId': pluginId, diff --git a/source/frontend/carla_host.py b/source/frontend/carla_host.py index dc73ea4f5..78bc9d3f7 100644 --- a/source/frontend/carla_host.py +++ b/source/frontend/carla_host.py @@ -54,6 +54,7 @@ if qt_config == 5: QListWidgetItem, QGraphicsView, QMainWindow, + QToolButton, ) elif qt_config == 6: @@ -86,6 +87,7 @@ elif qt_config == 6: QListWidgetItem, QGraphicsView, QMainWindow, + QToolButton, ) # ------------------------------------------------------------------------------------------------------------ @@ -101,6 +103,7 @@ from carla_shared import * from carla_settings import * from carla_utils import * from carla_widgets import * +from carla_skin import * from patchcanvas import patchcanvas from widgets.digitalpeakmeter import DigitalPeakMeter @@ -221,6 +224,8 @@ class HostWindow(QMainWindow): self.fOscAddressTCP = "" self.fOscAddressUDP = "" + self.slowTimer = 0 + if CARLA_OS_MAC: self.fMacClosingHelper = True @@ -270,6 +275,8 @@ class HostWindow(QMainWindow): self.fWithCanvas = withCanvas + self.fTweaks = {} + # ---------------------------------------------------------------------------------------------------- # Internal stuff (logs) @@ -293,6 +300,7 @@ class HostWindow(QMainWindow): if self.host.isControl: self.ui.act_file_new.setVisible(False) self.ui.act_file_open.setVisible(False) + self.ui.act_file_reload.setVisible(False) self.ui.act_file_save_as.setVisible(False) self.ui.tabUtils.removeTab(0) else: @@ -319,10 +327,12 @@ class HostWindow(QMainWindow): self.ui.act_file_new.setEnabled(False) self.ui.act_file_open.setEnabled(False) + self.ui.act_file_reload.setEnabled(False) self.ui.act_file_save.setEnabled(False) self.ui.act_file_save_as.setEnabled(False) self.ui.act_engine_stop.setEnabled(False) - self.ui.act_plugin_remove_all.setEnabled(False) + # self.ui.act_plugin_remove_all.setEnabled(False) + self.setMenuMacrosEnabled(False) self.ui.act_canvas_show_internal.setChecked(False) self.ui.act_canvas_show_internal.setVisible(False) @@ -503,6 +513,7 @@ class HostWindow(QMainWindow): self.ui.act_file_refresh.setIcon(getIcon('view-refresh', 16, 'svgz')) self.ui.act_file_new.setIcon(getIcon('document-new', 16, 'svgz')) self.ui.act_file_open.setIcon(getIcon('document-open', 16, 'svgz')) + self.ui.act_file_reload.setIcon(getIcon('view-refresh-purple', 16, 'svgz')) self.ui.act_file_save.setIcon(getIcon('document-save', 16, 'svgz')) self.ui.act_file_save_as.setIcon(getIcon('document-save-as', 16, 'svgz')) self.ui.act_file_quit.setIcon(getIcon('application-exit', 16, 'svgz')) @@ -511,8 +522,18 @@ class HostWindow(QMainWindow): self.ui.act_engine_panic.setIcon(getIcon('dialog-warning', 16, 'svgz')) self.ui.act_engine_config.setIcon(getIcon('configure', 16, 'svgz')) self.ui.act_plugin_add.setIcon(getIcon('list-add', 16, 'svgz')) - self.ui.act_plugin_add_jack.setIcon(getIcon('list-add', 16, 'svgz')) + self.ui.act_plugin_add_jack.setIcon(getIcon('add-jack', 16, 'svgz')) self.ui.act_plugin_remove_all.setIcon(getIcon('edit-delete', 16, 'svgz')) + self.ui.act_plugins_enable.setIcon(getIcon('system-turnon', 16, 'svgz')) + self.ui.act_plugins_disable.setIcon(getIcon('system-shutdown', 16, 'svgz')) + self.ui.act_plugins_bypass.setIcon(getIcon('dry', 16, 'svgz')) + self.ui.act_plugins_wet100.setIcon(getIcon('wet', 16, 'svgz')) + self.ui.act_plugins_mute.setIcon(getIcon('audio-volume-muted', 16, 'svgz')) + self.ui.act_plugins_volume100.setIcon(getIcon('audio-volume-medium', 16, 'svgz')) + self.ui.act_plugins_center.setIcon(getIcon('balance', 16, 'svgz')) + self.ui.act_plugins_change_skin.setIcon(getIcon('skin', 16, 'svgz')) + self.ui.act_plugins_compact.setIcon(getIcon('compact', 16, 'svgz')) + self.ui.act_plugins_expand.setIcon(getIcon('restore', 16, 'svgz')) self.ui.act_canvas_arrange.setIcon(getIcon('view-sort-ascending', 16, 'svgz')) self.ui.act_canvas_refresh.setIcon(getIcon('view-refresh', 16, 'svgz')) self.ui.act_canvas_zoom_fit.setIcon(getIcon('zoom-fit-best', 16, 'svgz')) @@ -534,6 +555,7 @@ class HostWindow(QMainWindow): self.ui.act_file_new.triggered.connect(self.slot_fileNew) self.ui.act_file_open.triggered.connect(self.slot_fileOpen) + self.ui.act_file_reload.triggered.connect(self.slot_fileReload) self.ui.act_file_save.triggered.connect(self.slot_fileSave) self.ui.act_file_save_as.triggered.connect(self.slot_fileSaveAs) @@ -553,10 +575,12 @@ class HostWindow(QMainWindow): self.ui.act_plugins_wet100.triggered.connect(self.slot_pluginsWet100) self.ui.act_plugins_bypass.triggered.connect(self.slot_pluginsBypass) self.ui.act_plugins_center.triggered.connect(self.slot_pluginsCenter) + self.ui.act_plugins_change_skin.triggered.connect(self.slot_pluginsChangeSkin) self.ui.act_plugins_compact.triggered.connect(self.slot_pluginsCompact) self.ui.act_plugins_expand.triggered.connect(self.slot_pluginsExpand) self.ui.act_settings_show_toolbar.toggled.connect(self.slot_showToolbar) + self.ui.act_settings_show_toolbar_text.toggled.connect(self.slot_showToolbarText) self.ui.act_settings_show_meters.toggled.connect(self.slot_showCanvasMeters) self.ui.act_settings_show_keyboard.toggled.connect(self.slot_showCanvasKeyboard) self.ui.act_settings_show_side_panel.toggled.connect(self.slot_showSidePanel) @@ -738,7 +762,7 @@ class HostWindow(QMainWindow): self.host.set_custom_data(pluginId, CUSTOM_DATA_TYPE_PROPERTY, "CarlaColor", colorStr) pitem.recreateWidget(newColor = color) - def changePluginSkin(self, pluginId, skin): + def changePluginSkin(self, pluginId, skin, color = None): if pluginId > self.fPluginCount: return @@ -748,7 +772,9 @@ class HostWindow(QMainWindow): return self.host.set_custom_data(pluginId, CUSTOM_DATA_TYPE_PROPERTY, "CarlaSkin", skin) - if skin not in ("default","rncbc","presets","mpresets"): + if color is not None: + pitem.recreateWidget(newSkin = skin, newColor = color) + elif skin not in ("default","rncbc","presets","mpresets"): pitem.recreateWidget(newSkin = skin, newColor = (255,255,255)) else: pitem.recreateWidget(newSkin = skin) @@ -935,6 +961,16 @@ class HostWindow(QMainWindow): self.loadProjectNow() self.fProjectFilename = filenameOld + if self.fTweaks.get('ShowReload', 0): + self.ui.act_file_reload.setEnabled(True) + self.ui.act_file_reload.setVisible(True) + + @pyqtSlot() + def slot_fileReload(self): + if not (self.fProjectFilename == ""): + self.pluginRemoveAll() + self.loadProjectNow() + @pyqtSlot() def slot_fileSave(self, saveAs=False): if self.fProjectFilename and not saveAs: @@ -1177,6 +1213,7 @@ class HostWindow(QMainWindow): if self.host.isPlugin or not self.fSessionManagerName: self.ui.act_file_open.setEnabled(False) + self.ui.act_file_reload.setEnabled(False) self.ui.act_file_save_as.setEnabled(False) @pyqtSlot(int, str) @@ -1226,7 +1263,8 @@ class HostWindow(QMainWindow): # Plugins def removeAllPlugins(self): - self.ui.act_plugin_remove_all.setEnabled(False) + # self.ui.act_plugin_remove_all.setEnabled(False) + self.setMenuMacrosEnabled(False) patchcanvas.handleAllPluginsRemoved() while self.ui.listWidget.takeItem(0): @@ -1343,6 +1381,12 @@ class HostWindow(QMainWindow): act = fmenu.addAction(p['name']) act.setData(p) act.triggered.connect(self.slot_favoritePluginAdd) + + if self.fSavedSettings[CARLA_KEY_MAIN_SYSTEM_ICONS]: + fmenu.setIcon(getIcon('add-from-favorites', 16, 'svgz')) + else: + fmenu.setIcon(QIcon(":/16x16/add-from-favorites.svgz")) + menu.addMenu(fmenu) menu.addAction(self.ui.act_plugin_remove_all) @@ -1359,6 +1403,7 @@ class HostWindow(QMainWindow): menu.addSeparator() menu.addAction(self.ui.act_plugins_center) menu.addSeparator() + menu.addAction(self.ui.act_plugins_change_skin) menu.addAction(self.ui.act_plugins_compact) menu.addAction(self.ui.act_plugins_expand) @@ -1454,7 +1499,7 @@ class HostWindow(QMainWindow): if pitem is None: break - pitem.getWidget().setInternalParameter(PLUGIN_CAN_VOLUME, 1.0) + pitem.getWidget().setInternalParameter(PARAMETER_VOLUME, 1.0) @pyqtSlot() def slot_pluginsMute(self): @@ -1465,7 +1510,7 @@ class HostWindow(QMainWindow): if pitem is None: break - pitem.getWidget().setInternalParameter(PLUGIN_CAN_VOLUME, 0.0) + pitem.getWidget().setInternalParameter(PARAMETER_VOLUME, 0.0) @pyqtSlot() def slot_pluginsWet100(self): @@ -1476,7 +1521,7 @@ class HostWindow(QMainWindow): if pitem is None: break - pitem.getWidget().setInternalParameter(PLUGIN_CAN_DRYWET, 1.0) + pitem.getWidget().setInternalParameter(PARAMETER_DRYWET, 1.0) @pyqtSlot() def slot_pluginsBypass(self): @@ -1487,7 +1532,7 @@ class HostWindow(QMainWindow): if pitem is None: break - pitem.getWidget().setInternalParameter(PLUGIN_CAN_DRYWET, 0.0) + pitem.getWidget().setInternalParameter(PARAMETER_DRYWET, 0.0) @pyqtSlot() def slot_pluginsCenter(self): @@ -1501,6 +1546,39 @@ class HostWindow(QMainWindow): pitem.getWidget().setInternalParameter(PARAMETER_BALANCE_LEFT, -1.0) pitem.getWidget().setInternalParameter(PARAMETER_BALANCE_RIGHT, 1.0) pitem.getWidget().setInternalParameter(PARAMETER_PANNING, 0.0) + pitem.getWidget().setInternalParameter(PARAMETER_FORTH, 0.0) + + @pyqtSlot() + def slot_pluginsChangeSkin(self): + skin = QInputDialog.getItem(self, self.tr("Change Skin"), self.tr("Change Skin to:"), skinList, 0, False) + if not all(skin): + return + + if skin[0][:4] in ("calf", "clas", "zynf"): # These are non-colorable + for pluginId in range(self.fPluginCount): + gCarla.gui.changePluginSkin(pluginId, skin[0]) + return + + reColor = QInputDialog.getItem(self, self.tr("Change Color"), self.tr("Change Color mode:"), ['Follow Rules / As Is', 'Random'], 0, False) + if not all(reColor): + return + + color = None + if skin[0].startswith("3ba"): # These are dark tinted, need enlight. + color = (254,255,255) # If not random + luma = 0.5 + sat = 0.5 + elif skin[0].startswith("ope"): # These are dark tinted, need enlight. + luma = 0.5 + sat = 1.0 + else: + luma = 0.125 + sat = 0.25 + + for pluginId in range(self.fPluginCount): + if reColor[0][:2] == 'Ra': + color = QColor.fromHslF(random.random(), sat, luma, 1).getRgb()[0:3] + gCarla.gui.changePluginSkin(pluginId, skin[0], color) @pyqtSlot() def slot_pluginsCompact(self): @@ -1531,11 +1609,24 @@ class HostWindow(QMainWindow): self.fPluginList.append(pitem) self.fPluginCount += 1 - self.ui.act_plugin_remove_all.setEnabled(self.fPluginCount > 0) + self.setMenuMacrosEnabled(self.fPluginCount > 0) if pluginType == PLUGIN_LV2: self.fHasLoadedLv2Plugins = True + def setMenuMacrosEnabled(self, enabled): + self.ui.act_plugin_remove_all.setEnabled(enabled) + self.ui.act_plugins_enable.setEnabled(enabled) + self.ui.act_plugins_disable.setEnabled(enabled) + self.ui.act_plugins_volume100.setEnabled(enabled) + self.ui.act_plugins_mute.setEnabled(enabled) + self.ui.act_plugins_wet100.setEnabled(enabled) + self.ui.act_plugins_bypass.setEnabled(enabled) + self.ui.act_plugins_center.setEnabled(enabled) + self.ui.act_plugins_change_skin.setEnabled(enabled) + self.ui.act_plugins_compact.setEnabled(enabled) + self.ui.act_plugins_expand.setEnabled(enabled) + @pyqtSlot(int) def slot_handlePluginRemovedCallback(self, pluginId): if self.fWithCanvas: @@ -1558,7 +1649,8 @@ class HostWindow(QMainWindow): del pitem if self.fPluginCount == 0: - self.ui.act_plugin_remove_all.setEnabled(False) + # self.ui.act_plugin_remove_all.setEnabled(False) + self.setMenuMacrosEnabled(False) if self.fCurrentlyRemovingAllPlugins: self.fCurrentlyRemovingAllPlugins = False self.projectLoadingFinished(False) @@ -1569,7 +1661,9 @@ class HostWindow(QMainWindow): pitem = self.fPluginList[i] pitem.setPluginId(i) - self.ui.act_plugin_remove_all.setEnabled(True) + # self.ui.act_plugin_remove_all.setEnabled(True) + self.setMenuMacrosEnabled(True) + # -------------------------------------------------------------------------------------------------------- # Canvas @@ -1975,6 +2069,7 @@ class HostWindow(QMainWindow): settings.setValue("Geometry", self.saveGeometry()) settings.setValue("ShowToolbar", self.ui.toolBar.isEnabled()) + settings.setValue("ShowToolbarText", self.ui.act_settings_show_toolbar_text.isChecked()) settings.setValue("ShowSidePanel", self.ui.dockWidget.isEnabled()) diskFolders = [] @@ -2009,11 +2104,24 @@ class HostWindow(QMainWindow): showToolbar = settings.value("ShowToolbar", True, bool) self.ui.act_settings_show_toolbar.setChecked(showToolbar) + self.ui.act_settings_show_toolbar_text.setEnabled(showToolbar) self.ui.toolBar.blockSignals(True) self.ui.toolBar.setEnabled(showToolbar) self.ui.toolBar.setVisible(showToolbar) self.ui.toolBar.blockSignals(False) + showToolbarText = settings.value("ShowToolbarText", True, bool) + self.ui.act_settings_show_toolbar_text.setChecked(showToolbarText) + self.ui.toolBar.blockSignals(True) + + for btn in self.ui.toolBar.findChildren(QToolButton): + if showToolbarText: + btn.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) + else: + btn.setToolButtonStyle(Qt.ToolButtonIconOnly) + + self.ui.toolBar.blockSignals(False) + #if settings.contains("SplitterState"): #self.ui.splitter.restoreState(settings.value("SplitterState", b"")) #else: @@ -2066,6 +2174,7 @@ class HostWindow(QMainWindow): CARLA_KEY_MAIN_REFRESH_INTERVAL: settings.value(CARLA_KEY_MAIN_REFRESH_INTERVAL, CARLA_DEFAULT_MAIN_REFRESH_INTERVAL, int), CARLA_KEY_MAIN_SYSTEM_ICONS: settings.value(CARLA_KEY_MAIN_SYSTEM_ICONS, CARLA_DEFAULT_MAIN_SYSTEM_ICONS, bool), CARLA_KEY_MAIN_EXPERIMENTAL: settings.value(CARLA_KEY_MAIN_EXPERIMENTAL, CARLA_DEFAULT_MAIN_EXPERIMENTAL, bool), + CARLA_KEY_MAIN_SKIN_TWEAKS: settings.value(CARLA_KEY_MAIN_SKIN_TWEAKS, CARLA_DEFAULT_MAIN_SKIN_TWEAKS, str), CARLA_KEY_CANVAS_THEME: settings.value(CARLA_KEY_CANVAS_THEME, CARLA_DEFAULT_CANVAS_THEME, str), CARLA_KEY_CANVAS_SIZE: settings.value(CARLA_KEY_CANVAS_SIZE, CARLA_DEFAULT_CANVAS_SIZE, str), CARLA_KEY_CANVAS_AUTO_HIDE_GROUPS: settings.value(CARLA_KEY_CANVAS_AUTO_HIDE_GROUPS, CARLA_DEFAULT_CANVAS_AUTO_HIDE_GROUPS, bool), @@ -2182,6 +2291,19 @@ class HostWindow(QMainWindow): self.ui.toolBar.blockSignals(True) self.ui.toolBar.setEnabled(yesNo) self.ui.toolBar.setVisible(yesNo) + self.ui.act_settings_show_toolbar_text.setEnabled(yesNo) + self.ui.toolBar.blockSignals(False) + + @pyqtSlot(bool) + def slot_showToolbarText(self, yesNo): + self.ui.toolBar.blockSignals(True) + + for btn in self.ui.toolBar.findChildren(QToolButton): + if yesNo: + btn.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) + else: + btn.setToolButtonStyle(Qt.ToolButtonIconOnly) + self.ui.toolBar.blockSignals(False) @pyqtSlot(bool) @@ -2623,6 +2745,8 @@ class HostWindow(QMainWindow): self.ui.toolBar.setEnabled(visible) self.ui.toolBar.blockSignals(False) self.ui.act_settings_show_toolbar.setChecked(visible) + self.ui.act_settings_show_toolbar_text.setEnabled(visible) + @pyqtSlot(int) def slot_tabChanged(self, index): @@ -2889,7 +3013,6 @@ class HostWindow(QMainWindow): def idleFast(self): self.host.engine_idle() - self.refreshTransport() if self.fPluginCount == 0 or self.fCurrentlyRemovingAllPlugins: return @@ -2922,6 +3045,10 @@ class HostWindow(QMainWindow): def idleSlow(self): self.getAndRefreshRuntimeInfo() + self.slowTimer = (self.slowTimer + 1) % 4 + if (self.slowTimer == 0): + self.refreshTransport() # This one is CPU hungry. Ticket #1934 + if self.fPluginCount == 0 or self.fCurrentlyRemovingAllPlugins: return @@ -2949,6 +3076,13 @@ class HostWindow(QMainWindow): QMainWindow.changeEvent(self, event) def updateStyle(self): + loadTweaks(self) + + if self.fTweaks.get('MoreSpace', 0): + self.ui.pad_left.hide() + self.ui.pad_right.hide() + return + # Rack padding images setup rack_imgL = QImage(":/bitmaps/rack_padding_left.png") rack_imgR = QImage(":/bitmaps/rack_padding_right.png") diff --git a/source/frontend/carla_host_control.py b/source/frontend/carla_host_control.py index 8df839d3a..95f4d2123 100755 --- a/source/frontend/carla_host_control.py +++ b/source/frontend/carla_host_control.py @@ -189,6 +189,7 @@ class CarlaHostOSC(CarlaHostQtPlugin): "set_balance_left", "set_balance_right", "set_panning", + "set_forth", #"set_ctrl_channel", "set_parameter_value", "set_parameter_midi_channel", @@ -426,17 +427,18 @@ class CarlaControlServerTCP(Server): pluginId, index, type_, key, value = args self.host._set_customData(pluginId, index, { 'type': type_, 'key': key, 'value': value }) - @make_method('/ctrl/iparams', 'ifffffff') + @make_method('/ctrl/iparams', 'iffffffff') def carla_iparams(self, path, args): if DEBUG: print(path, args) self.fReceivedMsgs = True - pluginId, active, drywet, volume, balLeft, balRight, pan, ctrlChan = args + pluginId, active, drywet, volume, balLeft, balRight, pan, ctrlChan, forth = args self.host._set_internalValue(pluginId, PARAMETER_ACTIVE, active) self.host._set_internalValue(pluginId, PARAMETER_DRYWET, drywet) self.host._set_internalValue(pluginId, PARAMETER_VOLUME, volume) self.host._set_internalValue(pluginId, PARAMETER_BALANCE_LEFT, balLeft) self.host._set_internalValue(pluginId, PARAMETER_BALANCE_RIGHT, balRight) self.host._set_internalValue(pluginId, PARAMETER_PANNING, pan) + self.host._set_internalValue(pluginId, PARAMETER_FORTH, forth) self.host._set_internalValue(pluginId, PARAMETER_CTRL_CHANNEL, ctrlChan) @make_method('/ctrl/resp', 'is') diff --git a/source/frontend/carla_settings.py b/source/frontend/carla_settings.py index 80fff3340..5905f7196 100755 --- a/source/frontend/carla_settings.py +++ b/source/frontend/carla_settings.py @@ -47,6 +47,7 @@ from carla_backend import ( from carla_shared import ( CARLA_KEY_MAIN_PROJECT_FOLDER, CARLA_KEY_MAIN_USE_PRO_THEME, + CARLA_KEY_MAIN_SKIN_TWEAKS, CARLA_KEY_MAIN_PRO_THEME_COLOR, CARLA_KEY_MAIN_REFRESH_INTERVAL, CARLA_KEY_MAIN_CONFIRM_EXIT, @@ -115,6 +116,7 @@ from carla_shared import ( CARLA_DEFAULT_MAIN_CLASSIC_SKIN, CARLA_DEFAULT_MAIN_SHOW_LOGS, CARLA_DEFAULT_MAIN_SYSTEM_ICONS, + CARLA_DEFAULT_MAIN_SKIN_TWEAKS, #CARLA_DEFAULT_MAIN_EXPERIMENTAL, CARLA_DEFAULT_CANVAS_THEME, CARLA_DEFAULT_CANVAS_SIZE, @@ -705,6 +707,9 @@ class CarlaSettingsW(QDialog): self.ui.cb_main_theme_color.findText(settings.value(CARLA_KEY_MAIN_PRO_THEME_COLOR, CARLA_DEFAULT_MAIN_PRO_THEME_COLOR, str))) + self.ui.le_main_skin_tweaks.setText( + settings.value(CARLA_KEY_MAIN_SKIN_TWEAKS, CARLA_DEFAULT_MAIN_SKIN_TWEAKS, str)) + self.ui.sb_main_refresh_interval.setValue( settings.value(CARLA_KEY_MAIN_REFRESH_INTERVAL, CARLA_DEFAULT_MAIN_REFRESH_INTERVAL, int)) @@ -995,6 +1000,7 @@ class CarlaSettingsW(QDialog): settings.setValue(CARLA_KEY_MAIN_CONFIRM_EXIT, self.ui.ch_main_confirm_exit.isChecked()) settings.setValue(CARLA_KEY_MAIN_CLASSIC_SKIN, self.ui.cb_main_classic_skin_default.isChecked()) settings.setValue(CARLA_KEY_MAIN_USE_PRO_THEME, self.ui.ch_main_theme_pro.isChecked()) + settings.setValue(CARLA_KEY_MAIN_SKIN_TWEAKS, self.ui.le_main_skin_tweaks.text()) settings.setValue(CARLA_KEY_MAIN_PRO_THEME_COLOR, self.ui.cb_main_theme_color.currentText()) settings.setValue(CARLA_KEY_MAIN_REFRESH_INTERVAL, self.ui.sb_main_refresh_interval.value()) settings.setValue(CARLA_KEY_MAIN_SYSTEM_ICONS, self.ui.ch_main_system_icons.isChecked()) @@ -1193,6 +1199,7 @@ class CarlaSettingsW(QDialog): self.ui.group_main_theme.isEnabled()) self.ui.cb_main_theme_color.setCurrentIndex( self.ui.cb_main_theme_color.findText(CARLA_DEFAULT_MAIN_PRO_THEME_COLOR)) + self.ui.le_main_skin_tweaks.setText(CARLA_DEFAULT_MAIN_SKIN_TWEAKS) self.ui.sb_main_refresh_interval.setValue(CARLA_DEFAULT_MAIN_REFRESH_INTERVAL) self.ui.ch_main_confirm_exit.setChecked(CARLA_DEFAULT_MAIN_CONFIRM_EXIT) self.ui.cb_main_classic_skin_default(CARLA_DEFAULT_MAIN_CLASSIC_SKIN) diff --git a/source/frontend/carla_shared.py b/source/frontend/carla_shared.py index aae07a0d0..851e652c5 100644 --- a/source/frontend/carla_shared.py +++ b/source/frontend/carla_shared.py @@ -8,7 +8,8 @@ import os import sys -from math import fmod +from math import fmod, log10 +import numpy as np # ------------------------------------------------------------------------------------------------------------ # Imports (Signal) @@ -63,6 +64,9 @@ from carla_backend import ( ENGINE_TRANSPORT_MODE_JACK, ) +from utils import QSafeSettings +import ast + # ------------------------------------------------------------------------------------------------------------ # Config @@ -184,6 +188,7 @@ CANVAS_EYECANDY_SMALL = 1 CARLA_KEY_MAIN_PROJECT_FOLDER = "Main/ProjectFolder" # str CARLA_KEY_MAIN_USE_PRO_THEME = "Main/UseProTheme" # bool CARLA_KEY_MAIN_PRO_THEME_COLOR = "Main/ProThemeColor" # str +CARLA_KEY_MAIN_SKIN_TWEAKS = "Main/SkinTweaks" # str CARLA_KEY_MAIN_REFRESH_INTERVAL = "Main/RefreshInterval" # int CARLA_KEY_MAIN_CONFIRM_EXIT = "Main/ConfirmExit" # bool CARLA_KEY_MAIN_CLASSIC_SKIN = "Main/ClassicSkin" # bool @@ -273,6 +278,7 @@ CARLA_DEFAULT_MAIN_CLASSIC_SKIN = False CARLA_DEFAULT_MAIN_SHOW_LOGS = bool(not CARLA_OS_WIN) CARLA_DEFAULT_MAIN_SYSTEM_ICONS = False CARLA_DEFAULT_MAIN_EXPERIMENTAL = False +CARLA_DEFAULT_MAIN_SKIN_TWEAKS = "'ShowPan':0, 'ShowForth':0, 'WetVolPush':0, 'WetVolPushLed':1, 'Tooltips':0, 'MoreSpace':0, 'WetVolOnCompact':0, 'SymmetricArc':1, 'GapAuto':0, 'ColorFollow':0, 'ShortenLabels':1, 'ButtonHaveLed':1, 'ColoredNeon':1, 'HighContrast':0, 'ShowDisabled':0, 'ShowOutputs':1, 'ShowButtons':1, 'Button3Pos':1, 'TwoLineLabels':0, 'GapMin':0, 'GapMax':100, 'ColorFrom':-0.1, 'ColorSpan':0.4, 'Auto7segSize':0, 'Auto7segWidth':1, 'ShowReload':0, 'ShowPrograms':0, 'ShowMidiPrograms':0, 'ShowProgramsOnCompact':0, 'ShowMidiProgramsOnCompact':0, " # Canvas CARLA_DEFAULT_CANVAS_THEME = "Modern Dark" @@ -647,18 +653,22 @@ else: # Find decimal points for a parameter, using step and stepSmall def countDecimalPoints(step, stepSmall): - if stepSmall >= 1.0: + if (stepSmall >= 1.0) or (step <= 0) or (stepSmall <= 0): return 0 if step >= 1.0: return 2 - count = 0 - value = fmod(abs(step), 1) - while 0.0001 < value < 0.999 and count < 6: - value = fmod(value*10, 1) - count += 1 +# OLD + # count = 0 + # value = fmod(abs(step), 1) + # while 0.0001 < value < 0.999 and count < 6: + # value = fmod(value*10, 1) + # count += 1 + # + # return count - return count +# NEW: Looks like better handling of small values. + return -int(log10(stepSmall)) + 2 # ------------------------------------------------------------------------------------------------------------ # Check if a value is a number (float support) @@ -927,5 +937,44 @@ def CustomMessageBox(parent, icon, title, text, # pylint: enable=no-value-for-parameter # pylint: enable=too-many-arguments +# ------------------------------------------------------------------------------------------------------------ +# Tweaks, in form of 'Parameter':Value or 'skinnameParameter':Value, are holds both per-rack and per-plugin fine-tuning values (tweaks). + +def loadTweaks(self): + settings = QSafeSettings("falkTX", "Carla2") + skinTweaks = settings.value(CARLA_KEY_MAIN_SKIN_TWEAKS, CARLA_DEFAULT_MAIN_SKIN_TWEAKS, str) + try: + self.fTweaks = ast.literal_eval('{' + skinTweaks + '}') + except ValueError as e: + print("ERROR while parse `" + skinTweaks + "` :", e) + +# ------------------------------------------------------------------------------------------------------------ + +def getPrefixSuffix(unit): + prefix = "" + suffix = unit.strip() + + if suffix == "(coef)": + prefix = "* " + suffix = "" + else: + suffix = " " + suffix + + return prefix, suffix + +# ------------------------------------------------------------------------------------------------------------ + +def strLim(value, digits = 5): + result = np.format_float_positional(value, trim='-', fractional=False, precision=digits) + if len(result) > 9: + return '{:.3e}'.format(value) + else: + return result + +# ------------------------------------------------------------------------------------------------------------ +# Geometry + +RACK_KNOB_GAP = 5 + # ------------------------------------------------------------------------------------------------------------ # pylint: enable=possibly-used-before-assignment diff --git a/source/frontend/carla_skin.py b/source/frontend/carla_skin.py index 2eaba1ccd..82f59df64 100644 --- a/source/frontend/carla_skin.py +++ b/source/frontend/carla_skin.py @@ -6,15 +6,19 @@ # Imports (Global) from qt_compat import qt_config +import math +import random +import operator +from operator import itemgetter if qt_config == 5: from PyQt5.QtCore import Qt, QRectF, QLineF, QTimer from PyQt5.QtGui import QColor, QFont, QFontDatabase, QPainter, QPainterPath, QPen - from PyQt5.QtWidgets import QColorDialog, QFrame, QLineEdit, QPushButton + from PyQt5.QtWidgets import QColorDialog, QFrame, QLineEdit, QPushButton, QComboBox, QSizePolicy elif qt_config == 6: from PyQt6.QtCore import Qt, QRectF, QLineF, QTimer from PyQt6.QtGui import QColor, QFont, QFontDatabase, QPainter, QPainterPath, QPen - from PyQt6.QtWidgets import QColorDialog, QFrame, QLineEdit, QPushButton + from PyQt6.QtWidgets import QColorDialog, QFrame, QLineEdit, QPushButton, QComboBox, QSizePolicy # ------------------------------------------------------------------------------------------------------------ # Imports (Custom) @@ -29,7 +33,6 @@ from carla_backend import * from carla_shared import * from carla_widgets import * from widgets.digitalpeakmeter import DigitalPeakMeter -from widgets.paramspinbox import CustomInputDialog from widgets.scalabledial import ScalableDial # ------------------------------------------------------------------------------------------------------------ @@ -126,7 +129,7 @@ def getParameterShortName(paramName): # Get RGB colors for a plugin category def getColorFromCategory(category): - r = 40 + r = 39 g = 40 b = 40 @@ -152,45 +155,35 @@ def getColorFromCategory(category): return (r, g, b) # ------------------------------------------------------------------------------------------------------------ -# - -def setScalableDialStyle(widget, parameterId, parameterCount, whiteLabels, skinStyle): - if skinStyle.startswith("calf"): - widget.setCustomPaintMode(ScalableDial.CUSTOM_PAINT_MODE_NO_GRADIENT) - widget.setImage(7) - - elif skinStyle.startswith("openav"): - widget.setCustomPaintMode(ScalableDial.CUSTOM_PAINT_MODE_NO_GRADIENT) - if parameterId == PARAMETER_DRYWET: - widget.setImage(13) - elif parameterId == PARAMETER_VOLUME: - widget.setImage(12) - else: - widget.setImage(11) - - else: - if parameterId == PARAMETER_DRYWET: - widget.setCustomPaintMode(ScalableDial.CUSTOM_PAINT_MODE_CARLA_WET) - elif parameterId == PARAMETER_VOLUME: - widget.setCustomPaintMode(ScalableDial.CUSTOM_PAINT_MODE_CARLA_VOL) - - else: - _r = 255 - int((float(parameterId)/float(parameterCount))*200.0) - _g = 55 + int((float(parameterId)/float(parameterCount))*200.0) - _b = 0 #(r-40)*4 - widget.setCustomPaintColor(QColor(_r, _g, _b)) - widget.setCustomPaintMode(ScalableDial.CUSTOM_PAINT_MODE_COLOR) - - if whiteLabels: - colorEnabled = QColor("#BBB") - colorDisabled = QColor("#555") - else: - colorEnabled = QColor("#111") - colorDisabled = QColor("#AAA") - - widget.setLabelColor(colorEnabled, colorDisabled) - widget.setImage(3) +skinList = [ + "default", + "3bandeq", + "rncbc", + "calf_black", + "calf_blue", + "classic", + "openav-old", + "openav", + "zynfx", + "presets", + "mpresets", + "tube", + ] + +skinListTweakable = [ + "default", + "calf", + "openav", + "zynfx", + "tube", + ] + +def arrayIndex(array, value): + for index, item in enumerate(array): + if item.startswith(value): + return index + return 0 # ------------------------------------------------------------------------------------------------------------ # Abstract plugin slot @@ -235,6 +228,16 @@ class AbstractPluginSlot(QFrame, PluginEditParentMeta): self.fAdjustViewableKnobCountScheduled = False + # load fresh skin tweaks + self.fTweaks = {} + loadTweaks(self) + + # take panel color hue & sat to make "follow panel" paint + color = QColor(skinColor[0], skinColor[1], skinColor[2]) + hue = color.hueF() % 1.0 + sat = color.saturationF() + self.fColorHint = int(hue * 100) + int(sat * 100) / 100.0 # 50.80: 50% hue, 80% sat + # used during testing self.fIdleTimerId = 0 @@ -269,6 +272,8 @@ class AbstractPluginSlot(QFrame, PluginEditParentMeta): self.w_knobs_right = None self.spacer_knobs = None + self.slowTimer = 0 + # ------------------------------------------------------------- # Set-up connections @@ -348,8 +353,31 @@ class AbstractPluginSlot(QFrame, PluginEditParentMeta): if self.fEditDialog is not None and self.fPluginId == pluginId: self.customUiStateChanged(state) + # @pyqtSlot(int, int, int) + # def slot_handleParameterKnobVisible(self, pluginId, index, value): + # if self.fEditDialog is not None and self.fPluginId == pluginId: + # self.setKnobVisible(index, value) + + # @pyqtSlot(bool) + # def slot_knobVisible(self, value): + # self.host.set_drywet(self.fPluginId, value) + # self.setParameterValue(PARAMETER_DRYWET, value, True) + + @pyqtSlot(float) + def slot_dryWetChanged(self, value): + self.host.set_drywet(self.fPluginId, value) + self.setParameterValue(PARAMETER_DRYWET, value, True) + + @pyqtSlot(float) + def slot_volumeChanged(self, value): + self.host.set_volume(self.fPluginId, value) + self.setParameterValue(PARAMETER_VOLUME, value, True) + # ------------------------------------------------------------------ + def tweak(self, skinName, tweakName, default): + return self.fTweaks.get(skinName + tweakName, self.fTweaks.get(tweakName, default)) + def ready(self): self.fIsActive = bool(self.host.get_internal_parameter_value(self.fPluginId, PARAMETER_ACTIVE) >= 0.5) @@ -481,6 +509,9 @@ class AbstractPluginSlot(QFrame, PluginEditParentMeta): self.peak_in.setMeterStyle(DigitalPeakMeter.STYLE_RNCBC) elif self.fSkinStyle.startswith("openav") or self.fSkinStyle == "zynfx": self.peak_in.setMeterStyle(DigitalPeakMeter.STYLE_OPENAV) + elif self.fSkinStyle == "tube": + self.peak_in.setMeterStyle(DigitalPeakMeter.STYLE_TUBE) + self.peak_in.setMeterLinesEnabled(False) if self.fPeaksInputCount == 0 and not isinstance(self, PluginSlot_Classic): self.peak_in.hide() @@ -496,6 +527,9 @@ class AbstractPluginSlot(QFrame, PluginEditParentMeta): self.peak_out.setMeterStyle(DigitalPeakMeter.STYLE_RNCBC) elif self.fSkinStyle.startswith("openav") or self.fSkinStyle == "zynfx": self.peak_out.setMeterStyle(DigitalPeakMeter.STYLE_OPENAV) + elif self.fSkinStyle == "tube": + self.peak_out.setMeterStyle(DigitalPeakMeter.STYLE_TUBE) + self.peak_out.setMeterLinesEnabled(False) if self.fPeaksOutputCount == 0 and not isinstance(self, PluginSlot_Classic): self.peak_out.hide() @@ -530,7 +564,8 @@ class AbstractPluginSlot(QFrame, PluginEditParentMeta): styleSheet2 = "background-image: url(:/bitmaps/background_%s.png);" % self.fSkinStyle else: styleSheet2 = "background-color: rgb(200, 200, 200);" - styleSheet2 += "background-image: url(:/bitmaps/background_noise1.png);" + if self.fSkinStyle not in ("classic"): + styleSheet2 += "background-image: url(:/bitmaps/background_noise1.png);" if not self.fDarkStyle: colorEnabled = "#111" @@ -551,12 +586,63 @@ class AbstractPluginSlot(QFrame, PluginEditParentMeta): styleSheet += """ QComboBox#cb_presets, + QComboBox#cb_presets0, + QComboBox#cb_presets1, QLabel#label_audio_in, QLabel#label_audio_out, QLabel#label_midi { font-size: 10px; } """ self.setStyleSheet(styleSheet) + # ------------------------------------------------------------- + # Wet and Vol knobs on compacted slot + + # If "long" style not in "shorts" list, it will be matched to 0 ("default"). + skinNum = arrayIndex(skinListTweakable, self.fSkinStyle[: 3]) + skinName = skinListTweakable [skinNum] + + wetVolOnCompact = self.tweak(skinName, 'WetVolOnCompact', 0) + showDisabled = self.tweak(skinName, 'ShowDisabled', 0) + showOutputs = self.tweak(skinName, 'ShowOutputs', 0) + shortenLabels = self.tweak(skinName, 'ShortenLabels', 1) + btn3state = self.tweak(skinName, 'Button3Pos', 1) + + if isinstance(self, PluginSlot_Compact): + if self.fPluginInfo['hints'] & PLUGIN_CAN_DRYWET or showDisabled: + + + self.dial0 = ScalableDial(self, PARAMETER_DRYWET, 100, 1.0, 0.0, 1.0, "Dry/Wet", ScalableDial.CUSTOM_PAINT_MODE_CARLA_WET_MINI, -1, "%", self.fSkinStyle, whiteLabels, self.fTweaks) + self.dial0.setObjectName("dial0") + self.ui.horizontalLayout_2.insertWidget(6, self.dial0) + + if wetVolOnCompact: + self.dial0.setEnabled(bool(self.fPluginInfo['hints'] & PLUGIN_CAN_DRYWET)) + self.dial0.dragStateChanged.connect(self.slot_parameterDragStateChanged) + self.dial0.realValueChanged.connect(self.slot_dryWetChanged) + self.dial0.customContextMenuRequested.connect(self.slot_knobCustomMenu) + self.dial0.blockSignals(True) + self.dial0.setValue(self.host.get_internal_parameter_value(self.fPluginId, PARAMETER_DRYWET)) + self.dial0.blockSignals(False) + else: + self.dial0.hide() + + if self.fPluginInfo['hints'] & PLUGIN_CAN_VOLUME or showDisabled: + # self.dial1 = ScalableDial(self.ui.dial1, PARAMETER_VOLUME, 254, 1.0, 0.0, 1.27, "Volume", ScalableDial.CUSTOM_PAINT_MODE_CARLA_VOL_MINI, 0, "%", self.fSkinStyle, whiteLabels, self.fTweaks) + self.dial1 = ScalableDial(self, PARAMETER_VOLUME, 127, 1.0, 0.0, 1.27, "Volume", ScalableDial.CUSTOM_PAINT_MODE_CARLA_VOL_MINI, -1, "%", self.fSkinStyle, whiteLabels, self.fTweaks) + self.dial1.setObjectName("dial1") + self.ui.horizontalLayout_2.insertWidget(6, self.dial1) + + if wetVolOnCompact: + self.dial1.setEnabled(bool(self.fPluginInfo['hints'] & PLUGIN_CAN_VOLUME)) + self.dial1.dragStateChanged.connect(self.slot_parameterDragStateChanged) + self.dial1.realValueChanged.connect(self.slot_volumeChanged) + self.dial1.customContextMenuRequested.connect(self.slot_knobCustomMenu) + self.dial1.blockSignals(True) + self.dial1.setValue(self.host.get_internal_parameter_value(self.fPluginId, PARAMETER_VOLUME)) + self.dial1.blockSignals(False) + else: + self.dial1.hide() + # ------------------------------------------------------------- # Set-up parameters @@ -565,6 +651,11 @@ class AbstractPluginSlot(QFrame, PluginEditParentMeta): index = 0 layout = self.w_knobs_left.layout() + + # Rainbow paint, default is deep red -> green. Span can be negative. + hueFrom = self.tweak(skinName, 'ColorFrom', -0.03) + hueSpan = self.tweak(skinName, 'ColorSpan', 0.4) + for i in range(parameterCount): # 50 should be enough for everybody, right? if index >= 50: @@ -573,64 +664,153 @@ class AbstractPluginSlot(QFrame, PluginEditParentMeta): paramInfo = self.host.get_parameter_info(self.fPluginId, i) paramData = self.host.get_parameter_data(self.fPluginId, i) paramRanges = self.host.get_parameter_ranges(self.fPluginId, i) - isInteger = (paramData['hints'] & PARAMETER_IS_INTEGER) != 0 + default = self.host.get_default_parameter_value(self.fPluginId, i) + minimum = paramRanges['min'] + maximum = paramRanges['max'] + isEnabled = (paramData['hints'] & PARAMETER_IS_ENABLED) != 0 + isOutput = (paramData['type'] != PARAMETER_INPUT) + isBoolean = (paramData['hints'] & PARAMETER_IS_BOOLEAN) != 0 + isInteger = ((paramData['hints'] & PARAMETER_IS_INTEGER) != 0) or isBoolean - if paramData['type'] != PARAMETER_INPUT: - continue - if paramData['hints'] & PARAMETER_IS_BOOLEAN: - continue - if (paramData['hints'] & PARAMETER_IS_ENABLED) == 0: - continue - if (paramData['hints'] & PARAMETER_USES_SCALEPOINTS) != 0 and not isInteger: - # NOTE: we assume integer scalepoints are continuous - continue - if isInteger and paramRanges['max']-paramRanges['min'] <= 3: - continue if paramInfo['name'].startswith("unused"): + print("Carla: INFO: Parameter "+str(i)+" is Unused, so skipped.") continue - paramName = getParameterShortName(paramInfo['name']) + if not isEnabled: + print("Carla: INFO: Parameter "+str(i)+" is Disabled.") + if not showDisabled: + continue + + delta = maximum - minimum + if delta <= 0: + print("Carla: ERROR: Parameter "+str(i)+": Min, Max are same or wrong.") + return + + # NOTE: Booleans are mimic as isInteger with range [0 or 1]. + if btn3state: + isButton = (isInteger and (minimum == 0) and (maximum in (1, 2))) + else: + isButton = (isInteger and (minimum == 0) and (maximum == 1)) + + vuMeter = 0 + precision = 1 + if isOutput: + if not showOutputs: + continue + vuMeter = ((minimum == 0) and ((maximum == 1) or (maximum == 100)))\ + or (minimum == -maximum) # from -N to N, is it good to use VU ? + else: + # Integers have somewhat more coarse step + if isInteger: + while delta > 50: + delta = int(math.ceil(delta / 2)) + precision = delta + + # Floats are finer-step smoothed + else: + # Pretty steps for most common values, like 1-2-5-10 scales, + # still not in its final form. + while delta > 200: + # Mantissa is near 2.5 + is25 = int(abs((log10(delta) % 1) - log10(2.5)) < 0.001) + delta = delta / (2.0 + is25 * 0.5) + + while delta < 100: + # Mantissa is near 2.0 + is25 = int(abs((log10(delta) % 1) - log10(2.0)) < 0.001) + delta = delta * (2.0 + is25 * 0.5) + + precision = math.ceil(delta) + + if precision <= 0: # suddenly... + print("Carla: ERROR: Parameter "+str(i)+": Precision "+str(precision)+" is wrong!") + return + + if shortenLabels: + label = getParameterShortName(paramInfo['name']) + else: + label = paramInfo['name'] + + widget = ScalableDial(self, i, + precision, + default, + minimum, + maximum, + label, + skinNum * 16, + self.fColorHint, + paramInfo['unit'], + self.fSkinStyle, + whiteLabels, + self.fTweaks, + isInteger, + isButton, + isOutput, + vuMeter, + 1 ) # isVisible Experiment (index % 2) + + widget.setEnabled(isEnabled) - widget = ScalableDial(self, i) - widget.setLabel(paramName) - widget.setMinimum(paramRanges['min']) - widget.setMaximum(paramRanges['max']) widget.hide() - if isInteger: - widget.setPrecision(paramRanges['max']-paramRanges['min'], True) + scalePoints = [] + prefix = "" + suffix = "" + # NOTE: Issue #1983 + # if ((paramData['hints'] & PARAMETER_USES_SCALEPOINTS) != 0): + count = paramInfo['scalePointCount'] + if count: + for j in range(count): + scalePoints.append(self.host.get_parameter_scalepoint_info(self.fPluginId, i, j)) - setScalableDialStyle(widget, i, parameterCount, whiteLabels, self.fSkinStyle) + prefix, suffix = getPrefixSuffix(paramInfo['unit']) + widget.setScalePPS(sorted(scalePoints, key=operator.itemgetter("value")), prefix, suffix) index += 1 self.fParameterList.append([i, widget]) layout.addWidget(widget) - if self.w_knobs_right is not None and (self.fPluginInfo['hints'] & PLUGIN_CAN_DRYWET) != 0: - widget = ScalableDial(self, PARAMETER_DRYWET) - widget.setLabel("Dry/Wet") - widget.setMinimum(0.0) - widget.setMaximum(1.0) - setScalableDialStyle(widget, PARAMETER_DRYWET, 0, whiteLabels, self.fSkinStyle) + for i in range(index): + widget = layout.itemAt(i).widget() + if widget is not None: + coef = i/(index-1) if index > 1 else 0.5 # 0.5 = Midrange + hue = (hueFrom + coef * hueSpan) % 1.0 + widget.setCustomPaintColor(QColor.fromHslF(hue, 1, 0.5, 1)) - self.fParameterList.append([PARAMETER_DRYWET, widget]) - self.w_knobs_right.layout().addWidget(widget) - if self.w_knobs_right is not None and (self.fPluginInfo['hints'] & PLUGIN_CAN_VOLUME) != 0: - widget = ScalableDial(self, PARAMETER_VOLUME) - widget.setLabel("Volume") - widget.setMinimum(0.0) - widget.setMaximum(1.27) - setScalableDialStyle(widget, PARAMETER_VOLUME, 0, whiteLabels, self.fSkinStyle) + if self.w_knobs_right is not None: + if (self.fPluginInfo['hints'] & PLUGIN_CAN_DRYWET) != 0: + widget = ScalableDial(self, PARAMETER_DRYWET, 100, 1.0, 0.0, 1.0, "Dry/Wet", skinNum * 16 + ScalableDial.CUSTOM_PAINT_MODE_CARLA_WET, -1, "%", self.fSkinStyle, whiteLabels, self.fTweaks) + + self.fParameterList.append([PARAMETER_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) + + if (self.fPluginInfo['hints'] & PLUGIN_CAN_FORTH) != 0: + if widget.getTweak('ShowForth', 0): + widget = ScalableDial(self, PARAMETER_FORTH, 100, 0.0, -1.0, 1.0, "Forth", skinNum * 16 + ScalableDial.CUSTOM_PAINT_MODE_CARLA_FORTH, -1, "%", self.fSkinStyle, whiteLabels, self.fTweaks) + + self.fParameterList.append([PARAMETER_FORTH, widget]) + self.w_knobs_right.layout().addWidget(widget) - self.fParameterList.append([PARAMETER_VOLUME, widget]) - self.w_knobs_right.layout().addWidget(widget) for paramIndex, paramWidget in self.fParameterList: - paramWidget.setContextMenuPolicy(Qt.CustomContextMenu) - paramWidget.customContextMenuRequested.connect(self.slot_knobCustomMenu) - paramWidget.dragStateChanged.connect(self.slot_parameterDragStateChanged) - paramWidget.realValueChanged.connect(self.slot_parameterValueChanged) + if not paramWidget.fIsOutput: + paramWidget.customContextMenuRequested.connect(self.slot_knobCustomMenu) + paramWidget.dragStateChanged.connect(self.slot_parameterDragStateChanged) + paramWidget.realValueChanged.connect(self.slot_parameterValueChanged) paramWidget.blockSignals(True) paramWidget.setValue(self.host.get_internal_parameter_value(self.fPluginId, paramIndex)) paramWidget.blockSignals(False) @@ -721,9 +901,14 @@ class AbstractPluginSlot(QFrame, PluginEditParentMeta): if (self.fPluginInfo['hints'] & PLUGIN_CAN_PANNING) == 0: return self.host.set_panning(self.fPluginId, value) + elif parameterId == PARAMETER_FORTH: + if (self.fPluginInfo['hints'] & PLUGIN_CAN_FORTH) == 0: return + self.host.set_forth(self.fPluginId, value) + elif parameterId == PARAMETER_CTRL_CHANNEL: self.host.set_ctrl_channel(self.fPluginId, value) + self.setParameterValue(parameterId, value, True) self.fEditDialog.setParameterValue(parameterId, value) # ----------------------------------------------------------------- @@ -891,13 +1076,18 @@ class AbstractPluginSlot(QFrame, PluginEditParentMeta): paramWidget.setVisible(hints & PLUGIN_CAN_DRYWET) elif paramIndex == PARAMETER_VOLUME: paramWidget.setVisible(hints & PLUGIN_CAN_VOLUME) + # jpka: FIXME i add it, but can't trigger it for test, so disable to prevent possible crashes. Maybe it don't needed. + # self.dial0.setVisible(hints & PLUGIN_CAN_DRYWET) + # self.dial1.setVisible(hints & PLUGIN_CAN_VOLUME) + # print("self.dial0.setVisible(hints & PLUGIN_CAN_DRYWET)") if self.b_gui is not None: self.b_gui.setEnabled(bool(hints & PLUGIN_HAS_CUSTOM_UI)) + # NOTE: self.fParameterList is empty when compacted. def editDialogParameterValueChanged(self, pluginId, parameterId, value): for paramIndex, paramWidget in self.fParameterList: - if paramIndex != parameterId: + if (paramIndex != parameterId) or paramWidget.fIsOutput: continue paramWidget.blockSignals(True) @@ -905,6 +1095,16 @@ class AbstractPluginSlot(QFrame, PluginEditParentMeta): paramWidget.blockSignals(False) break + if isinstance(self, PluginSlot_Compact): + if (parameterId == PARAMETER_DRYWET) and (self.fPluginInfo['hints'] & PLUGIN_CAN_DRYWET): + self.dial0.blockSignals(True) + self.dial0.setValue(value) + self.dial0.blockSignals(False) + if (parameterId == PARAMETER_VOLUME) and (self.fPluginInfo['hints'] & PLUGIN_CAN_VOLUME): + self.dial1.blockSignals(True) + self.dial1.setValue(value) + self.dial1.blockSignals(False) + def editDialogProgramChanged(self, pluginId, index): if self.cb_presets is None: return @@ -997,6 +1197,23 @@ class AbstractPluginSlot(QFrame, PluginEditParentMeta): self.fEditDialog.idleSlow() + # 7-seg displays are pretty effective, but added frame skip will make it even better. + self.slowTimer = (self.slowTimer + 1) % 2 # Half the FPS, win some CPU. + + # NOTE: self.fParameterList is empty when compacted. + for paramIndex, paramWidget in self.fParameterList: + if not paramWidget.fIsOutput: + continue + # VU displays are CPU effective, make it run faster than 7-seg displays. + # if (self.slowTimer > 0) and (not paramWidget.fIsVuOutput): + if (self.slowTimer > 0): + continue + + paramWidget.blockSignals(True) # TODO Is it required for output? + value = self.host.get_current_parameter_value(self.fPluginId, paramIndex) + paramWidget.setValue(value, False) + paramWidget.blockSignals(False) + # ----------------------------------------------------------------- def drawOutline(self, painter): @@ -1023,7 +1240,9 @@ class AbstractPluginSlot(QFrame, PluginEditParentMeta): def updateParameterValues(self): for paramIndex, paramWidget in self.fParameterList: - if paramIndex < 0: + if paramIndex < 0: # DryWet and Volume + continue + if paramWidget.fIsOutput: continue paramWidget.blockSignals(True) @@ -1044,8 +1263,9 @@ class AbstractPluginSlot(QFrame, PluginEditParentMeta): # Expand/Minimize and Tweaks actCompact = menu.addAction(self.tr("Expand") if isinstance(self, PluginSlot_Compact) else self.tr("Minimize")) - actColor = menu.addAction(self.tr("Change Color...")) - actSkin = menu.addAction(self.tr("Change Skin...")) + actColor = menu.addAction(self.tr("Change Color...")) + actColorRandom = menu.addAction(self.tr("Random Color")) + actSkin = menu.addAction(self.tr("Change Skin...")) menu.addSeparator() # ------------------------------------------------------------- @@ -1157,28 +1377,17 @@ class AbstractPluginSlot(QFrame, PluginEditParentMeta): colorStr = "%i;%i;%i" % color gCarla.gui.changePluginColor(self.fPluginId, color, colorStr) - elif actSel == actSkin: - skinList = [ - "default", - "3bandeq", - "rncbc", - "calf_black", - "calf_blue", - "classic", - "openav-old", - "openav", - "zynfx", - "presets", - "mpresets", - ] - try: - index = skinList.index(self.fSkinStyle) - except: - index = 0 + elif actSel == actColorRandom: + hue = QColor(self.fSkinColor[0], self.fSkinColor[1], self.fSkinColor[2]).hueF() + color = QColor.fromHslF((hue + random.random()*0.5 + 0.25) % 1.0, 0.25, 0.125, 1).getRgb()[0:3] + colorStr = "%i;%i;%i" % color + gCarla.gui.changePluginColor(self.fPluginId, color, colorStr) + elif actSel == actSkin: skin = QInputDialog.getItem(self, self.tr("Change Skin"), self.tr("Change Skin to:"), - skinList, index, False) + skinList, arrayIndex(skinList, self.fSkinStyle), False) + if not all(skin): return gCarla.gui.changePluginSkin(self.fPluginId, skin[0]) @@ -1287,97 +1496,7 @@ class AbstractPluginSlot(QFrame, PluginEditParentMeta): @pyqtSlot() def slot_knobCustomMenu(self): - sender = self.sender() - index = sender.fIndex - minimum = sender.fMinimum - maximum = sender.fMaximum - current = sender.fRealValue - label = sender.fLabel - - if index in (PARAMETER_NULL, PARAMETER_CTRL_CHANNEL) or index <= PARAMETER_MAX: - return - elif index in (PARAMETER_DRYWET, PARAMETER_VOLUME): - default = 1.0 - elif index == PARAMETER_BALANCE_LEFT: - default = -1.0 - elif index == PARAMETER_BALANCE_RIGHT: - default = 1.0 - elif index == PARAMETER_PANNING: - default = 0.0 - else: - default = self.host.get_default_parameter_value(self.fPluginId, index) - - if index < PARAMETER_NULL: - # show in integer percentage - textReset = self.tr("Reset (%i%%)" % round(default*100.0)) - textMinim = self.tr("Set to Minimum (%i%%)" % round(minimum*100.0)) - textMaxim = self.tr("Set to Maximum (%i%%)" % round(maximum*100.0)) - else: - # show in full float value - textReset = self.tr("Reset (%f)" % default) - textMinim = self.tr("Set to Minimum (%f)" % minimum) - textMaxim = self.tr("Set to Maximum (%f)" % maximum) - - menu = QMenu(self) - actReset = menu.addAction(textReset) - menu.addSeparator() - actMinimum = menu.addAction(textMinim) - actCenter = menu.addAction(self.tr("Set to Center")) - actMaximum = menu.addAction(textMaxim) - menu.addSeparator() - actSet = menu.addAction(self.tr("Set value...")) - - if index > PARAMETER_NULL or index not in (PARAMETER_BALANCE_LEFT, PARAMETER_BALANCE_RIGHT, PARAMETER_PANNING): - menu.removeAction(actCenter) - - actSelected = menu.exec_(QCursor.pos()) - - if actSelected == actSet: - if index < PARAMETER_NULL: - value, ok = QInputDialog.getInt(self, self.tr("Set value"), label, round(current*100), round(minimum*100), round(maximum*100), 1) - - if not ok: - return - - value = float(value)/100.0 - - else: - paramInfo = self.host.get_parameter_info(self.fPluginId, index) - paramRanges = self.host.get_parameter_ranges(self.fPluginId, index) - scalePoints = [] - - for i in range(paramInfo['scalePointCount']): - scalePoints.append(self.host.get_parameter_scalepoint_info(self.fPluginId, index, i)) - - prefix = "" - suffix = paramInfo['unit'].strip() - - if suffix == "(coef)": - prefix = "* " - suffix = "" - else: - suffix = " " + suffix - - dialog = CustomInputDialog(self, label, current, minimum, maximum, - paramRanges['step'], paramRanges['stepSmall'], scalePoints, prefix, suffix) - - if not dialog.exec_(): - return - - value = dialog.returnValue() - - elif actSelected == actMinimum: - value = minimum - elif actSelected == actMaximum: - value = maximum - elif actSelected == actReset: - value = default - elif actSelected == actCenter: - value = 0.0 - else: - return - - sender.setValue(value, True) + PluginEdit.slot_knobCustomMenu(self) # ----------------------------------------------------------------- @@ -1439,9 +1558,23 @@ class AbstractPluginSlot(QFrame, PluginEditParentMeta): if index < 0: break - curWidth += widget.width() + 4 + if not widget.getIsVisible(): + continue + + curWidth += widget.width() + RACK_KNOB_GAP - if curWidth + widget.width() * 2 + 8 < maxWidth: + if self.fTweaks.get('MoreSpace', 0): + if self.w_knobs_right is None: # calf + limit = curWidth + else: + if QT_VERSION < 0x60000: + limit = curWidth + self.w_knobs_right.getContentsMargins()[0] + 8 + else: + limit = curWidth + 4 + 8 + else: + limit = curWidth + 56 + 8 + + if limit < maxWidth: #if not widget.isVisible(): widget.show() continue @@ -1719,6 +1852,12 @@ class PluginSlot_Compact(AbstractPluginSlot): self.peak_in = self.ui.peak_in self.peak_out = self.ui.peak_out + if self.fTweaks.get('ShowProgramsOnCompact', 0): + insertProgramList(self, self.ui.layout_peaks, 0) + + if self.fTweaks.get('ShowMidiProgramsOnCompact', 0): + insertMidiProgramList(self, self.ui.layout_peaks, 0) + self.ready() # ----------------------------------------------------------------- @@ -1756,11 +1895,24 @@ class PluginSlot_Default(AbstractPluginSlot): self.w_knobs_right = self.ui.w_knobs_right self.spacer_knobs = self.ui.layout_bottom.itemAt(1).spacerItem() + if self.fTweaks.get('MoreSpace', 0): + self.ui.layout_bottom.setContentsMargins(0, 4, 0, 0) + + if self.fTweaks.get('ShowPrograms', 0): + # insertProgramList(self, self.ui.layout_top, 6) + insertProgramList(self, self.ui.layout_peaks, 0) + + if self.fTweaks.get('ShowMidiPrograms', 0): + # insertMidiProgramList(self, self.ui.layout_top, 6) + insertMidiProgramList(self, self.ui.layout_peaks, 0) + self.ready() # ----------------------------------------------------------------- def getFixedHeight(self): + if self.fSkinStyle == "tube": + return 98 return 80 # ----------------------------------------------------------------- @@ -1839,13 +1991,17 @@ class PluginSlot_Presets(AbstractPluginSlot): self.peak_in = self.ui.peak_in self.peak_out = self.ui.peak_out - if skinStyle == "zynfx": + # if skinStyle == "zynfx": # TODO jpka: TEST ing zynfx as normal tweakable skin + if False: self.setupZynFxParams() else: self.w_knobs_left = self.ui.w_knobs_left self.w_knobs_right = self.ui.w_knobs_right self.spacer_knobs = self.ui.layout_bottom.itemAt(1).spacerItem() + if self.fTweaks.get('MoreSpace', 0): + self.ui.layout_bottom.setContentsMargins(0, 4, 0, 0) + self.ready() if usingMidiPrograms: @@ -1855,6 +2011,8 @@ class PluginSlot_Presets(AbstractPluginSlot): # ------------------------------------------------------------- + # it works only for internal zyn builds, which are disabled by default + # (?) not for just manual "zynfx" skin selection def setupZynFxParams(self): parameterCount = min(self.host.get_parameter_count(self.fPluginId), 8) @@ -2001,6 +2159,46 @@ class PluginSlot_Presets(AbstractPluginSlot): # ------------------------------------------------------------------------------------------------------------ +def insertProgramList(self, layout, index): + count = self.host.get_program_count(self.fPluginId) + if count: + cb = QComboBox(None) + cb.setObjectName("cb_presets0") # use this stylesheet + + for i in range(count): + string = self.host.get_program_name(self.fPluginId, i) + + if len(string) == 0: + print("Carla: WARNING: Program List have zero length item.") + return + + cb.addItem(string) + + layout.insertWidget(index, cb) + cb.setCurrentIndex(self.host.get_current_program_index(self.fPluginId)) + cb.currentIndexChanged.connect(self.slot_programChanged) + +def insertMidiProgramList(self, layout, index): + count = self.host.get_midi_program_count(self.fPluginId) + if count: + cb = QComboBox(None) + cb.setObjectName("cb_presets1") # use this stylesheet + + for i in range(count): + string = self.host.get_midi_program_data(self.fPluginId, i)['name'] + + if len(string) == 0: + print("Carla: WARNING: MIDI Program List have zero length item.") + return + + cb.addItem(string) + + layout.insertWidget(index, cb) + cb.setCurrentIndex(self.host.get_current_midi_program_index(self.fPluginId)) + cb.currentIndexChanged.connect(self.slot_midiProgramChanged) + +# ------------------------------------------------------------------------------------------------------------ + def getColorAndSkinStyle(host, pluginId): pluginInfo = host.get_plugin_info(pluginId) pluginName = host.get_real_plugin_name(pluginId) @@ -2018,9 +2216,9 @@ def getColorAndSkinStyle(host, pluginId): # Samplers if pluginInfo['type'] == PLUGIN_SF2: - return (colorCategory, "sf2") + return (colorCategory, "mpresets") if pluginInfo['type'] == PLUGIN_SFZ: - return (colorCategory, "sfz") + return (colorCategory, "mpresets") # Calf if pluginName.split(" ", 1)[0].lower() == "calf": @@ -2032,6 +2230,10 @@ def getColorAndSkinStyle(host, pluginId): if pluginMaker == "OpenAV": return (colorNone, "openav") + # Tube + if "tube" in pluginLabel: + return (colorCategory, "tube") + # ZynFX if pluginInfo['type'] == PLUGIN_INTERNAL: if pluginLabel.startswith("zyn") and pluginInfo['category'] != PLUGIN_CATEGORY_SYNTH: @@ -2042,7 +2244,8 @@ def getColorAndSkinStyle(host, pluginId): return (colorNone, "zynfx") if pluginInfo['type'] == PLUGIN_LV2: - if pluginLabel.startswith("http://kxstudio.sf.net/carla/plugins/zyn") and pluginName != "ZynAddSubFX": + # if pluginLabel.startswith("http://kxstudio.sf.net/carla/plugins/zyn") and pluginName != "ZynAddSubFX": + if pluginLabel.startswith("http://kxstudio.sf.net/carla/plugins/zyn") and pluginName != "ZynAddSubFX" or "zyn" in pluginLabel: # jpka: TEST ing zynfx as normal tweakable skin return (colorNone, "zynfx") # Presets diff --git a/source/frontend/carla_widgets.py b/source/frontend/carla_widgets.py index 0a90cdd12..82a00a4f4 100755 --- a/source/frontend/carla_widgets.py +++ b/source/frontend/carla_widgets.py @@ -6,6 +6,7 @@ # Imports (Global) from abc import abstractmethod +import numpy as np # ------------------------------------------------------------------------------------------------------------ # Imports (PyQt) @@ -14,7 +15,7 @@ from qt_compat import qt_config if qt_config == 5: from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QByteArray - from PyQt5.QtGui import QCursor, QIcon, QPalette, QPixmap + from PyQt5.QtGui import QCursor, QIcon, QPalette, QPixmap, QFont from PyQt5.QtWidgets import ( QDialog, QFileDialog, @@ -24,10 +25,18 @@ if qt_config == 5: QScrollArea, QVBoxLayout, QWidget, + QGraphicsScene, + QGraphicsTextItem, + QGraphicsView, + QTableWidget, + QTableWidgetItem, + QHeaderView, + QLabel, + QSizePolicy, ) elif qt_config == 6: from PyQt6.QtCore import pyqtSignal, pyqtSlot, Qt, QByteArray - from PyQt6.QtGui import QCursor, QIcon, QPalette, QPixmap + from PyQt6.QtGui import QCursor, QIcon, QPalette, QPixmap, QFont from PyQt6.QtWidgets import ( QDialog, QFileDialog, @@ -37,6 +46,14 @@ elif qt_config == 6: QScrollArea, QVBoxLayout, QWidget, + QGraphicsScene, + QGraphicsTextItem, + QGraphicsView, + QTableWidget, + QTableWidgetItem, + QHeaderView, + QLabel, + QSizePolicy, ) # ------------------------------------------------------------------------------------------------------------ @@ -60,6 +77,7 @@ from carla_backend import ( PLUGIN_CAN_VOLUME, PLUGIN_CAN_BALANCE, PLUGIN_CAN_PANNING, + PLUGIN_CAN_FORTH, PLUGIN_CATEGORY_SYNTH, PLUGIN_OPTION_FIXED_BUFFERS, PLUGIN_OPTION_FORCE_STEREO, @@ -72,11 +90,13 @@ from carla_backend import ( PLUGIN_OPTION_SEND_ALL_SOUND_OFF, PLUGIN_OPTION_SEND_PROGRAM_CHANGES, PLUGIN_OPTION_SKIP_SENDING_NOTES, + PARAMETER_NULL, PARAMETER_DRYWET, PARAMETER_VOLUME, PARAMETER_BALANCE_LEFT, PARAMETER_BALANCE_RIGHT, PARAMETER_PANNING, + PARAMETER_FORTH, PARAMETER_CTRL_CHANNEL, PARAMETER_IS_ENABLED, PARAMETER_IS_AUTOMATABLE, @@ -84,6 +104,7 @@ from carla_backend import ( PARAMETER_USES_SCALEPOINTS, PARAMETER_USES_CUSTOM_TEXT, PARAMETER_CAN_BE_CV_CONTROLLED, + parameterHintsText, PARAMETER_INPUT, PARAMETER_OUTPUT, CONTROL_INDEX_NONE, CONTROL_INDEX_MIDI_PITCHBEND, @@ -94,6 +115,7 @@ from carla_backend import ( from carla_shared import ( MIDI_CC_LIST, MAX_MIDI_CC_LIST_ITEM, countDecimalPoints, + strLim, fontMetricsHorizontalAdvance, setUpSignals, gCarla @@ -103,6 +125,8 @@ from carla_utils import getPluginTypeAsString from widgets.collapsablewidget import CollapsibleBox from widgets.pixmapkeyboard import PixmapKeyboardHArea +from widgets.paramspinbox import CustomInputDialog, ParamSpinBox +from widgets.scalabledial import ScalableDial # ------------------------------------------------------------------------------------------------------------ # Carla GUI defines @@ -119,6 +143,7 @@ class PluginParameter(QWidget): mappedControlChanged = pyqtSignal(int, int) mappedRangeChanged = pyqtSignal(int, float, float) midiChannelChanged = pyqtSignal(int, int) + knobVisibilityChanged = pyqtSignal(int, int) valueChanged = pyqtSignal(int, float) def __init__(self, parent, host, pInfo, pluginId, tabIndex): @@ -141,6 +166,7 @@ class PluginParameter(QWidget): self.fParameterId = pInfo['index'] self.fPluginId = pluginId self.fTabIndex = tabIndex + self.fKnobVisible = True # ------------------------------------------------------------- # Set-up GUI @@ -157,6 +183,7 @@ class PluginParameter(QWidget): self.ui.widget.setStep(pInfo['step']) self.ui.widget.setStepSmall(pInfo['stepSmall']) self.ui.widget.setStepLarge(pInfo['stepLarge']) + # NOTE: Issue #1983 self.ui.widget.setScalePoints(pInfo['scalePoints'], bool(pHints & PARAMETER_USES_SCALEPOINTS)) if pInfo['comment']: @@ -536,41 +563,26 @@ class PluginEdit(QDialog): labelPluginFont.setWeight(75) self.ui.label_plugin.setFont(labelPluginFont) - self.ui.dial_drywet.setCustomPaintMode(self.ui.dial_drywet.CUSTOM_PAINT_MODE_CARLA_WET) - self.ui.dial_drywet.setImage(3) - self.ui.dial_drywet.setLabel("Dry/Wet") - self.ui.dial_drywet.setMinimum(0.0) - self.ui.dial_drywet.setMaximum(1.0) + pluginHints = self.host.get_plugin_info(self.fPluginId)['hints'] + + self.ui.dial_drywet = ScalableDial(self.ui.dial_drywet, PARAMETER_DRYWET, 100, 1.0, 0.0, 1.0, "Dry/Wet", ScalableDial.CUSTOM_PAINT_MODE_CARLA_WET) self.ui.dial_drywet.setValue(host.get_internal_parameter_value(pluginId, PARAMETER_DRYWET)) - self.ui.dial_vol.setCustomPaintMode(self.ui.dial_vol.CUSTOM_PAINT_MODE_CARLA_VOL) - self.ui.dial_vol.setImage(3) - self.ui.dial_vol.setLabel("Volume") - self.ui.dial_vol.setMinimum(0.0) - self.ui.dial_vol.setMaximum(1.27) + self.ui.dial_vol = ScalableDial(self.ui.dial_vol, PARAMETER_VOLUME, 127, 1.0, 0.0, 1.27, "Volume", ScalableDial.CUSTOM_PAINT_MODE_CARLA_VOL) self.ui.dial_vol.setValue(host.get_internal_parameter_value(pluginId, PARAMETER_VOLUME)) - self.ui.dial_b_left.setCustomPaintMode(self.ui.dial_b_left.CUSTOM_PAINT_MODE_CARLA_L) - self.ui.dial_b_left.setImage(4) - self.ui.dial_b_left.setLabel("L") - self.ui.dial_b_left.setMinimum(-1.0) - self.ui.dial_b_left.setMaximum(1.0) + self.ui.dial_b_left = ScalableDial(self.ui.dial_b_left, PARAMETER_BALANCE_LEFT, 100, -1.0, -1.0, 1.0, "L", ScalableDial.CUSTOM_PAINT_MODE_CARLA_L) self.ui.dial_b_left.setValue(host.get_internal_parameter_value(pluginId, PARAMETER_BALANCE_LEFT)) - self.ui.dial_b_right.setCustomPaintMode(self.ui.dial_b_right.CUSTOM_PAINT_MODE_CARLA_R) - self.ui.dial_b_right.setImage(4) - self.ui.dial_b_right.setLabel("R") - self.ui.dial_b_right.setMinimum(-1.0) - self.ui.dial_b_right.setMaximum(1.0) + self.ui.dial_b_right = ScalableDial(self.ui.dial_b_right, PARAMETER_BALANCE_RIGHT, 100, 1.0, -1.0, 1.0, "R", ScalableDial.CUSTOM_PAINT_MODE_CARLA_R) self.ui.dial_b_right.setValue(host.get_internal_parameter_value(pluginId, PARAMETER_BALANCE_RIGHT)) - self.ui.dial_pan.setCustomPaintMode(self.ui.dial_b_right.CUSTOM_PAINT_MODE_CARLA_PAN) - self.ui.dial_pan.setImage(4) - self.ui.dial_pan.setLabel("Pan") - self.ui.dial_pan.setMinimum(-1.0) - self.ui.dial_pan.setMaximum(1.0) + self.ui.dial_pan = ScalableDial(self.ui.dial_pan, PARAMETER_PANNING, 100, 0, -1.0, 1.0, "Pan", ScalableDial.CUSTOM_PAINT_MODE_CARLA_PAN) self.ui.dial_pan.setValue(host.get_internal_parameter_value(pluginId, PARAMETER_PANNING)) + self.ui.dial_forth = ScalableDial(self.ui.dial_forth, PARAMETER_FORTH, 100, 0, -1.0, 1.0, "Forth", ScalableDial.CUSTOM_PAINT_MODE_CARLA_FORTH) + self.ui.dial_forth.setValue(host.get_internal_parameter_value(pluginId, PARAMETER_FORTH)) + self.ui.sb_ctrl_channel.setValue(self.fControlChannel+1) self.ui.scrollArea = PixmapKeyboardHArea(self) @@ -582,10 +594,10 @@ class PluginEdit(QDialog): self.ui.scrollArea.setVisible(False) # todo - self.ui.rb_balance.setEnabled(False) - self.ui.rb_balance.setVisible(False) - self.ui.rb_pan.setEnabled(False) - self.ui.rb_pan.setVisible(False) + # self.ui.rb_balance.setEnabled(False) + # self.ui.rb_balance.setVisible(False) + # self.ui.rb_pan.setEnabled(False) + # self.ui.rb_pan.setVisible(False) flags = self.windowFlags() flags &= ~Qt.WindowContextHelpButtonHint @@ -617,6 +629,7 @@ class PluginEdit(QDialog): self.ui.dial_b_left.realValueChanged.connect(self.slot_balanceLeftChanged) self.ui.dial_b_right.realValueChanged.connect(self.slot_balanceRightChanged) self.ui.dial_pan.realValueChanged.connect(self.slot_panChanged) + self.ui.dial_forth.realValueChanged.connect(self.slot_forthChanged) self.ui.sb_ctrl_channel.valueChanged.connect(self.slot_ctrlChannelChanged) self.ui.dial_drywet.customContextMenuRequested.connect(self.slot_knobCustomMenu) @@ -751,6 +764,10 @@ class PluginEdit(QDialog): self.ui.dial_pan.setValue(self.host.get_internal_parameter_value(self.fPluginId, PARAMETER_PANNING)) self.ui.dial_pan.blockSignals(False) + self.ui.dial_forth.blockSignals(True) + self.ui.dial_forth.setValue(self.host.get_internal_parameter_value(self.fPluginId, PARAMETER_FORTH)) + self.ui.dial_forth.blockSignals(False) + self.fControlChannel = round(self.host.get_internal_parameter_value(self.fPluginId, PARAMETER_CTRL_CHANNEL)) self.ui.sb_ctrl_channel.blockSignals(True) self.ui.sb_ctrl_channel.setValue(self.fControlChannel+1) @@ -820,6 +837,7 @@ class PluginEdit(QDialog): self.ui.dial_b_left.setEnabled(pluginHints & PLUGIN_CAN_BALANCE) self.ui.dial_b_right.setEnabled(pluginHints & PLUGIN_CAN_BALANCE) self.ui.dial_pan.setEnabled(pluginHints & PLUGIN_CAN_PANNING) + self.ui.dial_forth.setEnabled(pluginHints & PLUGIN_CAN_FORTH) optsAvailable = self.fPluginInfo['optionsAvailable'] optsEnabled = self.fPluginInfo['optionsEnabled'] @@ -901,7 +919,6 @@ class PluginEdit(QDialog): break paramData = self.host.get_parameter_data(self.fPluginId, i) - if paramData['type'] not in (PARAMETER_INPUT, PARAMETER_OUTPUT): unusedParameters += 1 continue @@ -974,6 +991,12 @@ class PluginEdit(QDialog): self._createParameterWidgets(PARAMETER_INPUT, paramInputListFull, self.tr("Parameters")) self._createParameterWidgets(PARAMETER_OUTPUT, paramOutputListFull, self.tr("Outputs")) + # Create full parameter list table tab + self._createParameterXrayTab(self.tr("XRay")) + + # Create experimental description tab WORKINPROGRESS NOTE discussions/1967, pull/1961 + self._createDescriptionTab(self.tr("Description")) + # Restore tab state if tabIndex < self.ui.tabWidget.count(): self.ui.tabWidget.setCurrentIndex(tabIndex) @@ -1197,6 +1220,11 @@ class PluginEdit(QDialog): self.ui.dial_pan.setValue(value) self.ui.dial_pan.blockSignals(False) + elif index == PARAMETER_FORTH: + self.ui.dial_forth.blockSignals(True) + self.ui.dial_forth.setValue(value) + self.ui.dial_forth.blockSignals(False) + elif index == PARAMETER_CTRL_CHANNEL: self.fControlChannel = round(value) self.ui.sb_ctrl_channel.blockSignals(True) @@ -1396,6 +1424,13 @@ class PluginEdit(QDialog): if self.fParent is not None: self.fParent.editDialogParameterValueChanged(self.fPluginId, PARAMETER_PANNING, value) + @pyqtSlot(float) + def slot_forthChanged(self, value): + self.host.set_forth(self.fPluginId, value) + + if self.fParent is not None: + self.fParent.editDialogParameterValueChanged(self.fPluginId, PARAMETER_FORTH, value) + @pyqtSlot(int) def slot_ctrlChannelChanged(self, value): self.fControlChannel = value-1 @@ -1474,69 +1509,67 @@ class PluginEdit(QDialog): @pyqtSlot() def slot_knobCustomMenu(self): - sender = self.sender() - knobName = sender.objectName() - - if knobName == "dial_drywet": - minimum = 0.0 - maximum = 1.0 - default = 1.0 - label = "Dry/Wet" - elif knobName == "dial_vol": - minimum = 0.0 - maximum = 1.27 - default = 1.0 - label = "Volume" - elif knobName == "dial_b_left": - minimum = -1.0 - maximum = 1.0 - default = -1.0 - label = "Balance-Left" - elif knobName == "dial_b_right": - minimum = -1.0 - maximum = 1.0 - default = 1.0 - label = "Balance-Right" - elif knobName == "dial_pan": - minimum = -1.0 - maximum = 1.0 - default = 0.0 - label = "Panning" + # jpka: NOTE now Edit knobs are also know their constraints, so it's worth to set values as normal ones. + sender = self.sender() + index = sender.fIndex + minimum = sender.fMinimum + maximum = sender.fMaximum + current = sender.fRealValue + label = sender.fLabel + default = sender.fDefault + unit = sender.fUnit + step = stepSmall = 1.0 + + if index < PARAMETER_NULL: + percent = 100.0 else: - minimum = 0.0 - maximum = 1.0 - default = 0.5 - label = "Unknown" + percent = 1 + + textReset = self.tr("Reset (" + strLim(default * percent) + unit + ")\tR, Middle click") + textMinim = self.tr("Set to Minimum (" + strLim(minimum * percent) + unit + ")\t0") + textMaxim = self.tr("Set to Maximum (" + strLim(maximum * percent) + unit + ")\tEnd") + + if sender.fIsButton: + editHotKey = "E" + else: + editHotKey = "Enter, Double click" menu = QMenu(self) - actReset = menu.addAction(self.tr("Reset (%i%%)" % (default*100))) + actReset = menu.addAction(textReset) menu.addSeparator() - actMinimum = menu.addAction(self.tr("Set to Minimum (%i%%)" % (minimum*100))) - actCenter = menu.addAction(self.tr("Set to Center")) - actMaximum = menu.addAction(self.tr("Set to Maximum (%i%%)" % (maximum*100))) + actMinimum = menu.addAction(textMinim) + actCenter = menu.addAction(self.tr("Set to Center\t5")) + actMaximum = menu.addAction(textMaxim) menu.addSeparator() - actSet = menu.addAction(self.tr("Set value...")) + actSet = menu.addAction(self.tr("Set value...\t" + editHotKey)) - if label not in ("Balance-Left", "Balance-Right", "Panning"): + if index > PARAMETER_NULL or index not in (PARAMETER_BALANCE_LEFT, PARAMETER_BALANCE_RIGHT, PARAMETER_PANNING, PARAMETER_FORTH): menu.removeAction(actCenter) actSelected = menu.exec_(QCursor.pos()) if actSelected == actSet: - current = minimum + (maximum-minimum)*(float(sender.value())/10000) - value, ok = QInputDialog.getInt(self, - self.tr("Set value"), - label, - round(current*100.0), - round(minimum*100.0), - round(maximum*100.0), - 1) - if ok: - value = float(value)/100.0 + paramInfo = self.host.get_parameter_info(self.fPluginId, index) + paramRanges = self.host.get_parameter_ranges(self.fPluginId, index) + scalePoints = [] - if not ok: + for i in range(paramInfo['scalePointCount']): + scalePoints.append(self.host.get_parameter_scalepoint_info(self.fPluginId, index, i)) + + if sender.fIsInteger: + step = max(1, int((maximum - minimum)/100)) + stepSmall = max(1, int(step/10)) + else: + step = paramRanges['step'] * percent + stepSmall = paramRanges['stepSmall'] * percent + + dialog = CustomInputDialog(self, label, current * percent, minimum * percent, maximum * percent, step, stepSmall, scalePoints, "", "", unit) + + if not dialog.exec_(): return + value = dialog.returnValue() / percent + elif actSelected == actMinimum: value = minimum elif actSelected == actMaximum: @@ -1605,13 +1638,15 @@ class PluginEdit(QDialog): scrollAreaLayout = QVBoxLayout(scrollAreaWidget) scrollAreaLayout.setSpacing(3) + expandBox = (len(paramList) < 50) + for paramInfo in paramList: groupName = paramInfo['groupName'] if groupName: groupSymbol, groupName = groupName.split(":",1) groupLayout, groupWidget = groupWidgets.get(groupSymbol, (None, None)) if groupLayout is None: - groupWidget = CollapsibleBox(groupName, scrollAreaWidget) + groupWidget = CollapsibleBox(groupName, scrollAreaWidget, expandBox) groupLayout = groupWidget.getContentLayout() groupWidget.setPalette(palette2) scrollAreaLayout.addWidget(groupWidget) @@ -1678,6 +1713,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 = '' + bin(value)[2:] + hints = '' + for bit in range(len(parameterHintsText)): + if (value & int(2**(bit-1))): + hint = parameterHintsText[bit-1] + # toolTip += '
' + hint + hints += ', ' + hint + addCell('Data', name, str(value)) # , toolTip + '') + 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 = '

'\ + 'Note: This plugin collected some presets for you.
'\ + 'Use Edit tab, then Load State button.
' + + 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('\ +

' + realPluginName + '


'\ + '' + labelURI + '

'\ + '
' + strDescr + '
' +\ + strLoadState +\ + ''); + + scene.addItem(text) + view = QGraphicsView(scene, self) + + self.ui.tabWidget.addTab(view, tabPageName) + + #------------------------------------------------------------------ + def testTimer(self): self.fIdleTimerId = self.startTimer(50) diff --git a/source/frontend/dialogs/aboutdialog.cpp b/source/frontend/dialogs/aboutdialog.cpp index 0174472f6..31c7f1d00 100644 --- a/source/frontend/dialogs/aboutdialog.cpp +++ b/source/frontend/dialogs/aboutdialog.cpp @@ -61,6 +61,7 @@ AboutDialog::AboutDialog(QWidget* const parent, "" "/set_volume" " <f-value>" "" "/set_balance_left" " <f-value>" "" "/set_balance_right" " <f-value>" + "" "/set_forth" " <f-value>" "" "/set_panning" " <f-value>" "" "/set_parameter_value" " <i-index> <f-value>" "" "/set_parameter_midi_cc" " <i-index> <i-cc>" diff --git a/source/frontend/qt_compat.py b/source/frontend/qt_compat.py index 39a125c64..66fde8410 100644 --- a/source/frontend/qt_compat.py +++ b/source/frontend/qt_compat.py @@ -32,6 +32,8 @@ elif qt_config == 6: QMessageBox, QSizePolicy, QStyle, + QToolButton, + QToolBar, ) Qt.AlignCenter = Qt.AlignmentFlag.AlignCenter @@ -60,9 +62,16 @@ elif qt_config == 6: Qt.CrossCursor = Qt.CursorShape.CrossCursor Qt.OpenHandCursor = Qt.CursorShape.OpenHandCursor Qt.PointingHandCursor = Qt.CursorShape.PointingHandCursor + Qt.RightArrow = Qt.ArrowType.RightArrow + Qt.DownArrow = Qt.ArrowType.DownArrow Qt.SizeAllCursor = Qt.CursorShape.SizeAllCursor Qt.SizeHorCursor = Qt.CursorShape.SizeHorCursor + Qt.TextSelectableByMouse = Qt.TextInteractionFlag.TextSelectableByMouse + + Qt.ToolButtonIconOnly = Qt.ToolButtonStyle.ToolButtonIconOnly + Qt.ToolButtonTextBesideIcon = Qt.ToolButtonStyle.ToolButtonTextBesideIcon + Qt.black = Qt.GlobalColor.black Qt.blue = Qt.GlobalColor.blue Qt.cyan = Qt.GlobalColor.cyan @@ -116,6 +125,13 @@ elif qt_config == 6: Qt.Key_X = Qt.Key.Key_X Qt.Key_Y = Qt.Key.Key_Y Qt.Key_Z = Qt.Key.Key_Z + Qt.Key_Space = Qt.Key.Key_Space + Qt.Key_Enter = Qt.Key.Key_Enter + Qt.Key_Return = Qt.Key.Key_Return + Qt.Key_PageUp = Qt.Key.Key_PageUp + Qt.Key_PageDown = Qt.Key.Key_PageDown + Qt.Key_Home = Qt.Key.Key_Home + Qt.Key_End = Qt.Key.Key_End Qt.AltModifier = Qt.KeyboardModifier.AltModifier Qt.ControlModifier = Qt.KeyboardModifier.ControlModifier @@ -140,8 +156,10 @@ elif qt_config == 6: Qt.Horizontal = Qt.Orientation.Horizontal Qt.FlatCap = Qt.PenCapStyle.FlatCap + Qt.RoundCap = Qt.PenCapStyle.RoundCap Qt.MiterJoin = Qt.PenJoinStyle.MiterJoin + Qt.RoundJoin = Qt.PenJoinStyle.RoundJoin Qt.DashLine = Qt.PenStyle.DashLine Qt.NoPen = Qt.PenStyle.NoPen @@ -181,6 +199,7 @@ elif qt_config == 6: QDialog.exec_ = lambda d: d.exec() + QDialogButtonBox.Ok = QDialogButtonBox.StandardButton.Ok QDialogButtonBox.Reset = QDialogButtonBox.StandardButton.Reset QEvent.EnabledChange = QEvent.Type.EnabledChange @@ -225,6 +244,7 @@ elif qt_config == 6: QGraphicsView.MinimalViewportUpdate = QGraphicsView.ViewportUpdateMode.MinimalViewportUpdate QHeaderView.Fixed = QHeaderView.ResizeMode.Fixed + QHeaderView.ResizeToContents = QHeaderView.ResizeMode.ResizeToContents QLineEdit.Normal = QLineEdit.EchoMode.Normal diff --git a/source/frontend/widgets/collapsablewidget.py b/source/frontend/widgets/collapsablewidget.py index 75e86b25a..fa42f4132 100644 --- a/source/frontend/widgets/collapsablewidget.py +++ b/source/frontend/widgets/collapsablewidget.py @@ -33,7 +33,7 @@ class QToolButtonWithMouseTracking(QToolButton): QToolButton.leaveEvent(self, event) class CollapsibleBox(QFrame): - def __init__(self, title, parent): + def __init__(self, title, parent, startsExpanded = True): QFrame.__init__(self, parent) self.setFrameShape(QFrame.StyledPanel) @@ -42,10 +42,10 @@ class CollapsibleBox(QFrame): self.toggle_button = QToolButtonWithMouseTracking(self) self.toggle_button.setText(title) self.toggle_button.setCheckable(True) - self.toggle_button.setChecked(True) + self.toggle_button.setChecked(startsExpanded) self.toggle_button.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) self.toggle_button.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) - self.toggle_button.setArrowType(Qt.DownArrow) + # self.toggle_button.setArrowType(Qt.DownArrow) # Not deleted, just moved self.toggle_button.toggled.connect(self.toolButtonPressed) self.content_area = QWidget(self) @@ -59,6 +59,8 @@ class CollapsibleBox(QFrame): lay.addWidget(self.toggle_button) lay.addWidget(self.content_area) + self.toolButtonPressed(startsExpanded) # Set initial state + @pyqtSlot(bool) def toolButtonPressed(self, toggled): self.content_area.setVisible(toggled) diff --git a/source/frontend/widgets/commondial.py b/source/frontend/widgets/commondial.py index a615cdeae..2ac835e22 100644 --- a/source/frontend/widgets/commondial.py +++ b/source/frontend/widgets/commondial.py @@ -5,18 +5,23 @@ # --------------------------------------------------------------------------------------------------------------------- # Imports (Global) -from math import isnan +from math import isnan, log10 +import numpy as np from qt_compat import qt_config if qt_config == 5: - from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QPointF, QRectF + from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QPoint, QPointF, QRectF, QEvent, QTimer from PyQt5.QtGui import QColor, QFont, QLinearGradient, QPainter - from PyQt5.QtWidgets import QDial + from PyQt5.QtWidgets import QWidget, QToolTip, QInputDialog elif qt_config == 6: - from PyQt6.QtCore import pyqtSignal, pyqtSlot, Qt, QPointF, QRectF + from PyQt6.QtCore import pyqtSignal, pyqtSlot, Qt, QPoint, QPointF, QRectF, QEvent, QTimer from PyQt6.QtGui import QColor, QFont, QLinearGradient, QPainter - from PyQt6.QtWidgets import QDial + from PyQt6.QtWidgets import QWidget, QToolTip, QInputDialog + +from carla_shared import strLim +from widgets.paramspinbox import CustomInputDialog +from carla_backend import PARAMETER_NULL, PARAMETER_DRYWET, PARAMETER_VOLUME, PARAMETER_BALANCE_LEFT, PARAMETER_BALANCE_RIGHT, PARAMETER_PANNING, PARAMETER_FORTH # --------------------------------------------------------------------------------------------------------------------- # Widget Class @@ -25,7 +30,7 @@ elif qt_config == 6: #def updateSizes(self): #def paintDial(self, painter): -class CommonDial(QDial): +class CommonDial(QWidget): # enum CustomPaintMode CUSTOM_PAINT_MODE_NULL = 0 # default (NOTE: only this mode has label gradient) CUSTOM_PAINT_MODE_CARLA_WET = 1 # color blue-green gradient (reserved #3) @@ -33,9 +38,11 @@ class CommonDial(QDial): CUSTOM_PAINT_MODE_CARLA_L = 3 # color yellow (reserved #4) CUSTOM_PAINT_MODE_CARLA_R = 4 # color yellow (reserved #4) CUSTOM_PAINT_MODE_CARLA_PAN = 5 # color yellow (reserved #3) - CUSTOM_PAINT_MODE_COLOR = 6 # color, selectable (reserved #3) - CUSTOM_PAINT_MODE_ZITA = 7 # custom zita knob (reserved #6) + CUSTOM_PAINT_MODE_CARLA_FORTH = 6 # Experimental + CUSTOM_PAINT_MODE_COLOR = 7 # May be deprecated (unless zynfx internal mode) CUSTOM_PAINT_MODE_NO_GRADIENT = 8 # skip label gradient + CUSTOM_PAINT_MODE_CARLA_WET_MINI = 9 # for compacted slot + CUSTOM_PAINT_MODE_CARLA_VOL_MINI = 10 # for compacted slot # enum Orientation HORIZONTAL = 0 @@ -51,16 +58,37 @@ class CommonDial(QDial): dragStateChanged = pyqtSignal(bool) realValueChanged = pyqtSignal(float) - def __init__(self, parent, index): - QDial.__init__(self, parent) + def __init__(self, parent, index, precision, default, minimum, maximum, label, paintMode, colorHint, unit, skinStyle, whiteLabels, tweaks, isInteger, isButton, isOutput, isVuOutput, isVisible): + QWidget.__init__(self, parent) + + self.fIndex = index + self.fPrecision = precision + self.fDefault = default + self.fMinimum = minimum + self.fMaximum = maximum + self.fCustomPaintMode = paintMode + self.fColorHint = colorHint + self.fUnit = unit + self.fSkinStyle = skinStyle + self.fWhiteLabels = whiteLabels + self.fTweaks = tweaks + self.fIsInteger = isInteger + self.fIsButton = isButton + self.fIsOutput = isOutput + self.fIsVuOutput = isVuOutput + self.fIsVisible = isVisible self.fDialMode = self.MODE_LINEAR - self.fMinimum = 0.0 - self.fMaximum = 1.0 + self.fLabel = label + self.fLastLabel = "" self.fRealValue = 0.0 - self.fPrecision = 10000 - self.fIsInteger = False + self.fLastValue = self.fDefault + + self.fScalePoints = [] + self.fNumScalePoints = 0 + self.fScalePointsPrefix = "" + self.fScalePointsSuffix = "" self.fIsHovered = False self.fIsPressed = False @@ -69,9 +97,6 @@ class CommonDial(QDial): self.fLastDragPos = None self.fLastDragValue = 0.0 - self.fIndex = index - - self.fLabel = "" self.fLabelPos = QPointF(0.0, 0.0) self.fLabelFont = QFont(self.font()) self.fLabelFont.setPixelSize(8) @@ -97,75 +122,79 @@ class CommonDial(QDial): self.fLabelGradientRect = QRectF(0.0, 0.0, 0.0, 0.0) - self.fCustomPaintMode = self.CUSTOM_PAINT_MODE_NULL self.fCustomPaintColor = QColor(0xff, 0xff, 0xff) - # Fake internal value, custom precision - QDial.setMinimum(self, 0) - QDial.setMaximum(self, self.fPrecision) - QDial.setValue(self, 0) + self.addContrast = int(bool(self.getTweak('HighContrast', 0))) + self.colorFollow = bool(self.getTweak('ColorFollow', 0)) + self.knobPusheable = bool(self.getTweak('WetVolPush', 0)) + self.displayTooltip = bool(self.getTweak('Tooltips', 1)) - self.valueChanged.connect(self.slot_valueChanged) + # We have two group of knobs, non-repaintable (like in Edit dialog) and normal. + # For non-repaintable, we init sizes/color once here; + # for normals, it should be (re)inited separately: we do not init it here + # to save CPU, some parameters are not known yet, repaint need anyway. + if self.fColorHint == -1: + self.updateSizes() + + self.update() + +# self.valueChanged.connect(self.slot_valueChanged) # FIXME def forceWhiteLabelGradientText(self): self.fLabelGradientColor1 = QColor(0, 0, 0, 255) self.fLabelGradientColor2 = QColor(0, 0, 0, 0) self.fLabelGradientColorT = [Qt.white, Qt.darkGray] - def setLabelColor(self, enabled, disabled): - self.fLabelGradientColor1 = QColor(0, 0, 0, 255) - self.fLabelGradientColor2 = QColor(0, 0, 0, 0) - self.fLabelGradientColorT = [enabled, disabled] + # def setLabelColor(self, enabled, disabled): + # self.fLabelGradientColor1 = QColor(0, 0, 0, 255) + # self.fLabelGradientColor2 = QColor(0, 0, 0, 0) + # self.fLabelGradientColorT = [enabled, disabled] def getIndex(self): return self.fIndex - def setIndex(self, index): - self.fIndex = index - - def setPrecision(self, value, isInteger): - self.fPrecision = value - self.fIsInteger = isInteger - QDial.setMaximum(self, int(value)) - - def setMinimum(self, value): - self.fMinimum = value - - def setMaximum(self, value): - self.fMaximum = value - def rvalue(self): return self.fRealValue + def pushLabel(self, label): + if self.fLastLabel == "": + self.fLastLabel = self.fLabel + self.fLabel = label + self.updateSizes() + self.update() + + def popLabel(self): + if not (self.fLastLabel == ""): + self.fLabel = self.fLastLabel + self.fLastLabel = "" + self.updateSizes() + self.update() + + def setScalePPS(self, scalePoints, prefix, suffix): + self.fScalePoints = scalePoints + self.fNumScalePoints = len(self.fScalePoints) + self.fScalePointsPrefix = prefix + self.fScalePointsSuffix = suffix + def setValue(self, value, emitSignal=False): if self.fRealValue == value or isnan(value): return - if value <= self.fMinimum: - qtValue = 0 + if (not self.fIsOutput) and value <= self.fMinimum: self.fRealValue = self.fMinimum - elif value >= self.fMaximum: - qtValue = int(self.fPrecision) + elif (not self.fIsOutput) and value >= self.fMaximum: self.fRealValue = self.fMaximum + elif self.fIsInteger or (abs(value - int(value)) < 1e-8): # tiny "notch" + self.fRealValue = round(value) + else: - qtValue = round(float(value - self.fMinimum) / float(self.fMaximum - self.fMinimum) * self.fPrecision) self.fRealValue = value - # Block change signal, we'll handle it ourselves - self.blockSignals(True) - QDial.setValue(self, qtValue) - self.blockSignals(False) - if emitSignal: self.realValueChanged.emit(self.fRealValue) - def setCustomPaintMode(self, paintMode): - if self.fCustomPaintMode == paintMode: - return - - self.fCustomPaintMode = paintMode self.update() def setCustomPaintColor(self, color): @@ -173,76 +202,262 @@ class CommonDial(QDial): return self.fCustomPaintColor = color + self.updateSizes() self.update() - def setLabel(self, label): - if self.fLabel == label: - return + def getTweak(self, tweakName, default): + return self.fTweaks.get(self.fSkinStyle + tweakName, self.fTweaks.get(tweakName, default)) - self.fLabel = label - self.updateSizes() - self.update() + def getIsVisible(self): + # print (self.fIsVisible) + return self.fIsVisible @pyqtSlot(int) def slot_valueChanged(self, value): self.fRealValue = float(value)/self.fPrecision * (self.fMaximum - self.fMinimum) + self.fMinimum self.realValueChanged.emit(self.fRealValue) + # jpka: TODO should be replaced by common dialog, but + # PluginEdit.slot_knobCustomMenu(...) - not found, import not work. + # So this is copy w/o access to 'step's. + def knobCustomInputDialog(self): + if self.fIndex < PARAMETER_NULL: + percent = 100.0 + else: + percent = 1 + + if self.fIsInteger: + step = max(1, int((self.fMaximum - self.fMinimum)/100)) + stepSmall = max(1, int(step/10)) + else: + step = 10 ** (round(log10((self.fMaximum - self.fMinimum) * percent))-2) + stepSmall = step / 100 + + dialog = CustomInputDialog(self, self.fLabel, self.fRealValue * percent, self.fMinimum * percent, self.fMaximum * percent, step, stepSmall, self.fScalePoints, "", "", self.fUnit) + if not dialog.exec_(): + return + + self.setValue(dialog.returnValue() / percent, True) + + def enterEvent(self, event): + self.setFocus() self.fIsHovered = True if self.fHoverStep == self.HOVER_MIN: self.fHoverStep = self.HOVER_MIN + 1 - QDial.enterEvent(self, event) + self.update() + def leaveEvent(self, event): self.fIsHovered = False if self.fHoverStep == self.HOVER_MAX: self.fHoverStep = self.HOVER_MAX - 1 - QDial.leaveEvent(self, event) + self.update() + + + def nextScalePoint(self): + for i in range(self.fNumScalePoints): + value = self.fScalePoints[i]['value'] + if value > self.fRealValue: + self.setValue(value, True) + return + self.setValue(self.fScalePoints[0]['value'], True) + def mousePressEvent(self, event): - if self.fDialMode == self.MODE_DEFAULT: - QDial.mousePressEvent(self, event) + if self.fDialMode == self.MODE_DEFAULT or self.fIsOutput: return if event.button() == Qt.LeftButton: - self.fIsPressed = True - self.fLastDragPos = event.pos() - self.fLastDragValue = self.fRealValue - self.dragStateChanged.emit(True) + # if self.fNumScalePoints: + # self.nextScalePoint() + # + if self.fIsButton: + value = int(self.fRealValue) + 1; + if (value > self.fMaximum): + value = 0 + self.setValue(value, True) + else: + self.fIsPressed = True + self.fLastDragPos = event.pos() + self.fLastDragValue = self.fRealValue + self.dragStateChanged.emit(True) + + elif event.button() == Qt.MiddleButton: + if self.fIsOutput: + return + self.setValue(self.fDefault, True) + + + def mouseDoubleClickEvent(self, event): + if self.knobPusheable and self.fIndex in (PARAMETER_DRYWET, PARAMETER_VOLUME, PARAMETER_PANNING, PARAMETER_FORTH): # -3, -4, -7, -9 + return # Mutex with special Single Click + + if event.button() == Qt.LeftButton: + if self.fIsButton: + value = int(self.fRealValue) + 1; + if (value > self.fMaximum): + value = 0 + self.setValue(value, True) + else: + if self.fIsOutput: + return + + self.knobCustomInputDialog() + def mouseMoveEvent(self, event): - if self.fDialMode == self.MODE_DEFAULT: - QDial.mouseMoveEvent(self, event) + if self.fDialMode == self.MODE_DEFAULT or self.fIsOutput: return if not self.fIsPressed: return - diff = (self.fMaximum - self.fMinimum) / 4.0 - pos = event.pos() - dx = diff * float(pos.x() - self.fLastDragPos.x()) / self.width() - dy = diff * float(pos.y() - self.fLastDragPos.y()) / self.height() - value = self.fLastDragValue + dx - dy + pos = event.pos() + delta = (float(pos.x() - self.fLastDragPos.x()) - float(pos.y() - self.fLastDragPos.y())) / 10 - if value < self.fMinimum: - value = self.fMinimum - elif value > self.fMaximum: - value = self.fMaximum - elif self.fIsInteger: - value = float(round(value)) + mod = event.modifiers() + self.applyDelta(mod, delta, True) - self.setValue(value, True) def mouseReleaseEvent(self, event): - if self.fDialMode == self.MODE_DEFAULT: - QDial.mouseReleaseEvent(self, event) + if self.fDialMode == self.MODE_DEFAULT or self.fIsOutput: return if self.fIsPressed: self.fIsPressed = False self.dragStateChanged.emit(False) + if event.button() == Qt.LeftButton: + if event.pos() == self.fLastDragPos: + if self.fNumScalePoints: + self.nextScalePoint() + else: + self.knobPush() + + # NOTE: fLastLabel state managed @ scalabledial + def knobPush(self): + if self.knobPusheable and self.fIndex in (PARAMETER_DRYWET, PARAMETER_VOLUME, PARAMETER_PANNING, PARAMETER_FORTH): # -3, -4, -7, -9 + + if self.fLastLabel == "": # push value + self.fLastValue = self.fRealValue + self.setValue(0, True) # Thru or Mute + else: # pop value + self.setValue(self.fLastValue, True) + + + def applyDelta(self, mod, delta, anchor = False): + if self.fIsOutput: + return + + if self.fIsButton: + self.setValue(self.fRealValue + delta, True) + return + + if self.fIsInteger: # 4 to 50 ticks per revolution + if (mod & Qt.ShiftModifier): + delta = delta * 5 + elif (mod & Qt.ControlModifier): + delta = delta / min(int((self.fMaximum-self.fMinimum)/self.fPrecision), 5) + else: # Floats are 250 to 500 ticks per revolution +# jpka: 1. Should i use these steps? +# 2. And what do i do when i TODO add MODE_LOG along with MODE_LINEAR? +# 3. And they're too small for large ints like in TAP Reverb, and strange for scalepoints. +# paramRanges = self.host.get_parameter_ranges(self.fPluginId, i) +# paramRanges['step'], paramRanges['stepSmall'], paramRanges['stepLarge'] + if (mod & Qt.ControlModifier) and (mod & Qt.ShiftModifier): + delta = delta * 2/5 + elif (mod & Qt.ControlModifier): + delta = delta * 2 + elif (mod & Qt.ShiftModifier): + delta = delta * 50 + else: + delta = delta * 10 + + difference = float(self.fMaximum-self.fMinimum) * float(delta) / float(self.fPrecision) + + if anchor: + self.setValue((self.fLastDragValue + difference), True) + else: + self.setValue((self.fRealValue + difference), True) + + return + + + def wheelEvent(self, event): + if self.fIsOutput: + return + + direction = event.angleDelta().y() + if direction < 0: + delta = -1.0 + elif direction > 0: + delta = 1.0 + else: + return + + mod = event.modifiers() + self.applyDelta(mod, delta) + return + + + def keyPressEvent(self, event): + if self.fIsOutput: + return + + key = event.key() + mod = event.modifiers() + modsNone = not ((mod & Qt.ShiftModifier) | (mod & Qt.ControlModifier) | (mod & Qt.AltModifier)) + + if modsNone: + match key: + case Qt.Key_Space | Qt.Key_Enter | Qt.Key_Return : + if self.fIsButton: + value = int(self.fRealValue) + 1 + if (value > self.fMaximum): + value = 0 + self.setValue(value, True) + + elif not key == Qt.Key_Space: + self.knobCustomInputDialog() + else: + if self.fNumScalePoints: + self.nextScalePoint() + else: + self.knobPush() + + case Qt.Key_E: + self.knobCustomInputDialog() + + case key if Qt.Key_0 <= key <= Qt.Key_9: + if self.fIsInteger and (self.fMinimum == 0) and (self.fMaximum <= 10): + self.setValue(key-Qt.Key_0, True) + + else: + self.setValue(self.fMinimum + float(self.fMaximum-self.fMinimum)/10.0*(key-Qt.Key_0), True) + + case Qt.Key_Home: # NOTE: interferes with Canvas control hotkey + self.setValue(self.fMinimum, True) + + case Qt.Key_End: + self.setValue(self.fMaximum, True) + + case Qt.Key_D: + self.setValue(self.fDefault, True) + + case Qt.Key_R: + self.setValue(self.fDefault, True) + + match key: + case Qt.Key_PageDown: + self.applyDelta(mod, -1) + + case Qt.Key_PageUp: + self.applyDelta(mod, 1) + + return + + def paintEvent(self, event): painter = QPainter(self) event.accept() @@ -250,22 +465,120 @@ class CommonDial(QDial): painter.save() painter.setRenderHint(QPainter.Antialiasing, True) + enabled = int(bool(self.isEnabled())) + if enabled: + self.setContextMenuPolicy(Qt.CustomContextMenu) + else: + self.setContextMenuPolicy(Qt.NoContextMenu) + if self.fLabel: - if self.fCustomPaintMode == self.CUSTOM_PAINT_MODE_NULL: - painter.setPen(self.fLabelGradientColor2) - painter.setBrush(self.fLabelGradient) - painter.drawRect(self.fLabelGradientRect) + # if self.fCustomPaintMode == self.CUSTOM_PAINT_MODE_NULL: + # painter.setPen(self.fLabelGradientColor2) + # painter.setBrush(self.fLabelGradient) + # # painter.drawRect(self.fLabelGradientRect) FIXME restore gradients. + + luma = int(bool(self.fWhiteLabels)) - 0.5 + if enabled: + L = (luma * (1.6 + self.addContrast * 0.4)) / 2 + 0.5 + else: + L = (luma * (0.2 + self.addContrast * 0.2)) / 2 + 0.5 painter.setFont(self.fLabelFont) - painter.setPen(self.fLabelGradientColorT[0 if self.isEnabled() else 1]) + # painter.setPen(self.fLabelGradientColorT[0 if self.fIsEnabled() else 1]) + painter.setPen(QColor.fromHslF(0, 0, L, 1)) painter.drawText(self.fLabelPos, self.fLabel) - self.paintDial(painter) + X = self.fWidth / 2 + Y = self.fHeight / 2 + + S = enabled * 0.9 # saturation + + E = enabled * self.fHoverStep / 40 # enlight + L = 0.6 + E + if self.addContrast: + L = min(L + 0.3, 1) # luma + + normValue = float(self.fRealValue - self.fMinimum) / float(self.fMaximum - self.fMinimum) + # Work In Progress FIXME + H=0 + if self.fIsOutput: + if self.fIsButton: + # self.paintLed (painter, X, Y, H, S, L, E, normValue) + self.paintDisplay(painter, X, Y, H, S, L, E, normValue, enabled) + else: + self.paintDisplay(painter, X, Y, H, S, L, E, normValue, enabled) + else: + if self.fIsButton: + self.paintButton (painter, X, Y, H, S, L, E, normValue, enabled) + else: + self.paintDial (painter, X, Y, H, S, L, E, normValue, enabled) + + # Display tooltip, above the knob (OS-independent, unlike of mouse tooltip). + # Note, update/redraw Qt's tooltip eats much more CPU than expected, + # so we have tweak for turn it off. See also #1934. + if self.fHoverStep == self.HOVER_MAX and self.displayTooltip: + # First, we need to find exact or nearest match (index from value). + # It is also tests if we have scale points at all. + num = -1 + for i in range(self.fNumScalePoints): + scaleValue = self.fScalePoints[i]['value'] + if i == 0: + finalValue = scaleValue + num = 0 + else: + srange2 = abs(self.fRealValue - finalValue) + srange1 = abs(self.fRealValue - scaleValue) + if srange2 > srange1: + finalValue = scaleValue + num = i + if (srange1 == 0): # Exact match, save some CPU. + break + + tip = "" + if (num >= 0): # Scalepoints are used + tip = str(self.fScalePoints[num]['label']) + if not self.fIsButton: + tip = self.fScalePointsPrefix + \ + strLim(self.fScalePoints[num]['value']) + \ + self.fScalePointsSuffix + ": " + tip + # ? We most probably not need tooltip for button, if it is not scalepoint. + # elif not self.fIsButton: + else: + if self.fRealValue == 0 and self.fIndex == PARAMETER_DRYWET: #-3,-4,-7,-9 + tip = "THRU" + elif self.fRealValue == 0 and self.fIndex == PARAMETER_VOLUME: + tip = "MUTE" + elif self.fRealValue == 0 and self.fIndex == PARAMETER_PANNING: + tip = "Center" + elif self.fRealValue == 0 and self.fIndex == PARAMETER_FORTH: + tip = "Midway" + else: + if self.fIndex < PARAMETER_NULL: + percent = 100.0 + else: + percent = 1 + + tip = (strLim(self.fRealValue * percent) + " " + self.fUnit).strip() + if self.fIsOutput: + tip = tip + " [" + strLim(self.fMinimum * percent) + "..." + \ + strLim(self.fMaximum * percent) + "]" + + # Wrong vert. position for Calf: + # QToolTip.showText(self.mapToGlobal(QPoint(0, 0-self.geometry().height())), tip) + # FIXME Still wrong vert. position for QT_SCALE_FACTOR=2. + QToolTip.showText(self.mapToGlobal(QPoint(0, 0-45)), tip) + else: + QToolTip.hideText() + + if enabled: + if self.HOVER_MIN < self.fHoverStep < self.HOVER_MAX: + self.fHoverStep += 1 if self.fIsHovered else -1 + QTimer.singleShot(20, self.update) painter.restore() - def resizeEvent(self, event): - QDial.resizeEvent(self, event) - self.updateSizes() + # def resizeEvent(self, event): + # QWidget.resizeEvent(self, event) + # self.updateSizes() # --------------------------------------------------------------------------------------------------------------------- diff --git a/source/frontend/widgets/digitalpeakmeter.py b/source/frontend/widgets/digitalpeakmeter.py index 3707b296c..b85b4762d 100644 --- a/source/frontend/widgets/digitalpeakmeter.py +++ b/source/frontend/widgets/digitalpeakmeter.py @@ -35,6 +35,7 @@ class DigitalPeakMeter(QWidget): STYLE_OPENAV = 2 STYLE_RNCBC = 3 STYLE_CALF = 4 + STYLE_TUBE = 5 # ----------------------------------------------------------------------------------------------------------------- @@ -153,7 +154,7 @@ class DigitalPeakMeter(QWidget): if self.fMeterStyle == style: return - if style not in (self.STYLE_DEFAULT, self.STYLE_OPENAV, self.STYLE_RNCBC, self.STYLE_CALF): + if style not in (self.STYLE_DEFAULT, self.STYLE_OPENAV, self.STYLE_RNCBC, self.STYLE_CALF, self.STYLE_TUBE): qCritical(f"DigitalPeakMeter::setMeterStyle({style}) - invalid style") return @@ -163,7 +164,7 @@ class DigitalPeakMeter(QWidget): self.fMeterBackground = QColor("#1A1A1A") elif style == self.STYLE_RNCBC: self.fMeterBackground = QColor("#070707") - elif style == self.STYLE_CALF: + elif style in (self.STYLE_CALF, self.STYLE_TUBE): self.fMeterBackground = QColor("#000") if style == self.STYLE_CALF: @@ -215,23 +216,36 @@ class DigitalPeakMeter(QWidget): i = meter - 1 + if level < 0.001: + level = 0.0 + elif level > 0.999: + level = 1.0 + if self.fSmoothMultiplier > 0 and not forced: level = ( (self.fLastChannelData[i] * float(self.fSmoothMultiplier) + level) / float(self.fSmoothMultiplier + 1) ) - if level < 0.001: - level = 0.0 - elif level > 0.999: - level = 1.0 + self.fLastChannelData[i] = level + + # Discretize scale: for 10 points, first will lit at 5%, + # then 15%, and last at 95% of normalized value. + # We also win some CPU when not redraw at small changes. + if (self.fMeterStyle == self.STYLE_TUBE) and (level > 0.0) and (level < 1.0): + points = 20 + + # Transform to Sq Root domain: our meters have Sqrt dynamic compression; + # Discretize: + level = int(sqrt(level) * points + 0.5) / points + + # Transform back from Square Root domain: + level = level * level if self.fChannelData[i] != level: self.fChannelData[i] = level self.update() - self.fLastChannelData[i] = level - # ----------------------------------------------------------------------------------------------------------------- def updateGrandient(self): @@ -284,6 +298,14 @@ class DigitalPeakMeter(QWidget): self.fMeterGradient.setColorAt(0.0, self.fMeterColorBase) self.fMeterGradient.setColorAt(1.0, self.fMeterColorBase) + elif self.fMeterStyle == self.STYLE_TUBE: + color = QColor.fromHslF(0.9, 1, 0.6, 1) # Tuneon filled w/ neon + agron + points = 20 + for i in range(points + 1): + self.fMeterGradient.setColorAt(((i-0.3)/points % 1.0), color) + self.fMeterGradient.setColorAt(( i /points ), Qt.black) + self.fMeterGradient.setColorAt(((i+0.3)/points % 1.0), color) + self.updateGrandientFinalStop() def updateGrandientFinalStop(self): @@ -360,6 +382,13 @@ class DigitalPeakMeter(QWidget): meterPad += 2 meterSize -= 2 + elif self.fMeterStyle == self.STYLE_TUBE: + painter.setPen(QPen(Qt.NoPen)) + painter.setBrush(self.fMeterGradient) + meterPos += 3 + meterPad += 6 + meterSize -= 6 + else: painter.setPen(QPen(self.fMeterBackground, 0)) painter.setBrush(self.fMeterGradient) diff --git a/source/frontend/widgets/paramspinbox.py b/source/frontend/widgets/paramspinbox.py index 4a85a1669..eb7e937e3 100644 --- a/source/frontend/widgets/paramspinbox.py +++ b/source/frontend/widgets/paramspinbox.py @@ -25,7 +25,7 @@ elif qt_config == 6: import ui_inputdialog_value from carla_backend import CARLA_OS_MAC -from carla_shared import countDecimalPoints +from carla_shared import countDecimalPoints, getPrefixSuffix, strLim # ------------------------------------------------------------------------------------------------------------ # Get a fixed value within min/max bounds @@ -46,13 +46,21 @@ def geFixedValue(name, value, minimum, maximum): # Custom InputDialog with Scale Points support class CustomInputDialog(QDialog): - def __init__(self, parent, label, current, minimum, maximum, step, stepSmall, scalePoints, prefix, suffix): + def __init__(self, parent, label, current, minimum, maximum, step, stepSmall, scalePoints, prefix, suffix, unit=""): QDialog.__init__(self, parent) self.ui = ui_inputdialog_value.Ui_Dialog() self.ui.setupUi(self) - decimals = countDecimalPoints(step, stepSmall) - self.ui.label.setText(label) + if not (unit == ""): + prefix, suffix = getPrefixSuffix(unit) + + if unit == "%": + decimals = 1 + else: + decimals = countDecimalPoints(step, stepSmall) + + # self.ui.label.setText(label + " [" + strRound(self, minimum, decimals) + "..." + strRound(self, maximum, decimals) + "]") + self.ui.label.setText(label + " [" + strLim(minimum) + "..." + strLim(maximum) + "]") self.ui.doubleSpinBox.setDecimals(decimals) self.ui.doubleSpinBox.setRange(minimum, maximum) self.ui.doubleSpinBox.setSingleStep(step) @@ -361,14 +369,7 @@ class ParamSpinBox(QAbstractSpinBox): self.fStepLarge = value def setLabel(self, label): - prefix = "" - suffix = label.strip() - - if suffix == "(coef)": - prefix = "* " - suffix = "" - else: - suffix = " " + suffix + prefix, suffix = getPrefixSuffix(label) self.fLabelPrefix = prefix self.fLabelSuffix = suffix @@ -533,16 +534,16 @@ class ParamSpinBox(QAbstractSpinBox): pass menu = QMenu(self) - actReset = menu.addAction(self.tr("Reset (%f)" % self.fDefault)) + actReset = menu.addAction(self.tr("Reset (" + strLim(self.fDefault) + ")")) actRandom = menu.addAction(self.tr("Random")) menu.addSeparator() - actCopy = menu.addAction(self.tr("Copy (%f)" % self.fValue)) + actCopy = menu.addAction(self.tr("Copy (" + strLim(self.fValue) + ")")) if pasteValue is None: actPaste = menu.addAction(self.tr("Paste")) actPaste.setEnabled(False) else: - actPaste = menu.addAction(self.tr("Paste (%f)" % pasteValue)) + actPaste = menu.addAction(self.tr("Paste (" + strLim(pasteValue) + ")")) menu.addSeparator() diff --git a/source/frontend/widgets/racklistwidget.py b/source/frontend/widgets/racklistwidget.py index d64c5761e..30beb6839 100644 --- a/source/frontend/widgets/racklistwidget.py +++ b/source/frontend/widgets/racklistwidget.py @@ -13,11 +13,11 @@ import os from qt_compat import qt_config if qt_config == 5: - from PyQt5.QtCore import Qt, QSize, QRect, QEvent + from PyQt5.QtCore import QT_VERSION, Qt, QSize, QRect, QEvent from PyQt5.QtGui import QColor, QPainter, QPixmap from PyQt5.QtWidgets import QAbstractItemView, QListWidget, QListWidgetItem, QMessageBox elif qt_config == 6: - from PyQt6.QtCore import Qt, QSize, QRect, QEvent + from PyQt6.QtCore import QT_VERSION, Qt, QSize, QRect, QEvent, QPoint # QPoint is for Qt6 only. from PyQt6.QtGui import QColor, QPainter, QPixmap from PyQt6.QtWidgets import QAbstractItemView, QListWidget, QListWidgetItem, QMessageBox @@ -291,7 +291,10 @@ class RackListWidget(QListWidget): event.acceptProposedAction() - tryItem = self.itemAt(event.pos()) + if QT_VERSION < 0x60000: + tryItem = self.itemAt(event.pos()) + else: + tryItem = self.itemAt(QPoint(int(event.position().x()), int(event.position().y()))) if tryItem is not None: self.setCurrentRow(tryItem.getPluginId()) @@ -318,7 +321,10 @@ class RackListWidget(QListWidget): if not urls: return - tryItem = self.itemAt(event.pos()) + if QT_VERSION < 0x60000: + tryItem = self.itemAt(event.pos()) + else: + tryItem = self.itemAt(QPoint(int(event.position().x()), int(event.position().y()))) if tryItem is not None: pluginId = tryItem.getPluginId() diff --git a/source/frontend/widgets/scalabledial.py b/source/frontend/widgets/scalabledial.py index 0d842c738..fdf795f84 100644 --- a/source/frontend/widgets/scalabledial.py +++ b/source/frontend/widgets/scalabledial.py @@ -6,287 +6,903 @@ # Imports (Global) from math import cos, floor, pi, sin +import ast from qt_compat import qt_config if qt_config == 5: - from PyQt5.QtCore import pyqtSlot, Qt, QEvent, QPointF, QRectF, QTimer, QSize - from PyQt5.QtGui import QColor, QConicalGradient, QFontMetrics, QPainterPath, QPen, QPixmap - from PyQt5.QtSvg import QSvgWidget + from PyQt5.QtCore import pyqtSlot, Qt, QEvent, QPoint, QPointF, QRectF, QTimer, QSize + from PyQt5.QtGui import QColor, QLinearGradient, QRadialGradient, QConicalGradient, QFontMetrics, QPen, QPolygonF + from PyQt5.QtWidgets import QToolTip elif qt_config == 6: - from PyQt6.QtCore import pyqtSlot, Qt, QEvent, QPointF, QRectF, QTimer, QSize - from PyQt6.QtGui import QColor, QConicalGradient, QFontMetrics, QPainterPath, QPen, QPixmap - from PyQt6.QtSvgWidgets import QSvgWidget + from PyQt6.QtCore import pyqtSlot, Qt, QEvent, QPoint, QPointF, QRectF, QTimer, QSize + from PyQt6.QtGui import QColor, QLinearGradient, QRadialGradient, QConicalGradient, QFontMetrics, QPen, QPolygonF + from PyQt6.QtWidgets import QToolTip from .commondial import CommonDial -from carla_shared import fontMetricsHorizontalAdvance +from carla_shared import fontMetricsHorizontalAdvance, RACK_KNOB_GAP +from carla_backend import ( + PARAMETER_NULL, + PARAMETER_DRYWET, + PARAMETER_VOLUME, + PARAMETER_BALANCE_LEFT, + PARAMETER_BALANCE_RIGHT, + PARAMETER_PANNING, + PARAMETER_FORTH, + PARAMETER_MAX ) # --------------------------------------------------------------------------------------------------------------------- # Widget Class class ScalableDial(CommonDial): - def __init__(self, parent, index=0): - CommonDial.__init__(self, parent, index) - - self.fImage = QSvgWidget(":/scalable/dial_03.svg") - self.fImageNum = "01" + def __init__(self, parent, index, + precision, + default, + minimum, + maximum, + label, + paintMode, + colorHint = -1, # Hue & Sat, -1 = NotColorable + unit = "%", # Measurement Unit + skinStyle = "default", # Full name (from full list) + whiteLabels = 1, # Is light/white theme? + tweaks = {}, + isInteger = 0, # Input is Integer + isButton = 0, # Integer i/o is Button or LED + isOutput = 0, + isVuOutput = 0, # Output is analog VU meter + isVisible = 1 ): + + # self.fWidth = self.fHeight = 32 # aka fImageBaseSize, not includes label. + CommonDial.__init__(self, parent, index, precision, default, minimum, maximum, label, paintMode, colorHint, unit, skinStyle, whiteLabels, tweaks, isInteger, isButton, isOutput, isVuOutput, isVisible) + + # FIXME not every repaint need to re-calculate geometry? + def updateSizes(self): + knownModes = [ + # default + self.CUSTOM_PAINT_MODE_NULL , # 0 + self.CUSTOM_PAINT_MODE_CARLA_WET , # 1 + self.CUSTOM_PAINT_MODE_CARLA_VOL , # 2 + self.CUSTOM_PAINT_MODE_CARLA_L , # 3 + self.CUSTOM_PAINT_MODE_CARLA_R , # 4 + self.CUSTOM_PAINT_MODE_CARLA_PAN , # 5 + self.CUSTOM_PAINT_MODE_CARLA_FORTH , # 6 + self.CUSTOM_PAINT_MODE_CARLA_WET_MINI, # 9 + self.CUSTOM_PAINT_MODE_CARLA_VOL_MINI, # 10 + # calf + 16, + # openav + 32, 33, 34, 37, 38, + # zynfx + 48, 49, 50, 53, 54, + # tube + 64, 65, 66, 69, 70, + ] + + index = -1 + for i in range(len(knownModes)): + if knownModes[i] == self.fCustomPaintMode: + index = i + break + + if (index == -1): + print("Unknown paint mode "+ str(self.fCustomPaintMode)) + return - if self.fImage.sizeHint().width() > self.fImage.sizeHint().height(): - self.fImageOrientation = self.HORIZONTAL + self.skin = int(self.fCustomPaintMode / 16) + self.subSkin = int(self.fCustomPaintMode % 16) + + width, hueA, hueB, travel, radius, size, point, labelLift = [ + # default Aqua + [ 32, 0.50, 0.50, 260, 10, 10, 3 , 1/2, ], + [ 32, 0.3 , 0.50, 260, 10, 10, 3 , 1/2, ], # WET + [ 32, 0.50, 0.50, 260, 10, 10, 3 , 1/2, ], # VOL + [ 26, 0.21, 0.21, 260, 8, 10, 2.5, 1/2, ], # L + [ 26, 0.21, 0.21, 260, 8, 10, 2.5, 1/2, ], # R + [ 32, 0.50, 0.50, 260, 10, 10, 3 , 1/2, ], # PAN + [ 32, 0.50, 0.50, 260, 10, 10, 3 , 1/2, ], # FORTH + [ 28, 0.3 , 0.50, 260, 9, 10, 2.5, 1/2, ], # WET_MINI + [ 28, 0.50, 0.50, 260, 9, 10, 2.5, 1/2, ], # VOL_MINI + # calf Blue + [ 40, 0.53, 0.53, 290, 12, 12, 4 , 1 , ], # calf absent any wet/vol knobs + # openav Orange + [ 32, 0.05, 0.05, 270, 12, 12, 2.5, 2/3, ], + [ 32, 0.30, 0.5, 270, 12, 12, 2.5, 2/3, ], # WET + [ 32, 0.5, 0.5, 270, 12, 12, 2.5, 2/3, ], # VOL + [ 32, 0.5, 0.5, 270, 12, 12, 2.5, 2/3, ], + [ 32, 0.5, 0.5, 270, 12, 12, 2.5, 2/3, ], + # zynfx Teal + [ 38, 0.55, 0.55, 264, 12, 12, 4 , 1/4, ], + [ 38, 0.30, 0.5, 264, 12, 12, 4 , 1/4, ], # WET + [ 38, 0.5, 0.5, 264, 12, 12, 4 , 1/4, ], # VOL + [ 38, 0.5, 0.5, 264, 12, 12, 4 , 1/4, ], + [ 38, 0.5, 0.5, 264, 12, 12, 4 , 1/4, ], + # tube VFD + [ 50, 0.45, 0.45, 258, 12, 12, 4 , 1/2, ], + [ 50, 0.45, 0.45, 258, 12, 12, 4 , 1/2, ], # WET + [ 50, 0.45, 0.45, 258, 12, 12, 4 , 1/2, ], # VOL + [ 50, 0.45, 0.45, 258, 12, 12, 4 , 1/2, ], + [ 50, 0.45, 0.45, 258, 12, 12, 4 , 1/2, ], + ] [index] + + # Geometry & Color of controls & displays, some are tweakable: + # 1. Try to get value from per-skin tweak; + # 2. Then try to get value from common tweak; + # 3. Then use default value from array. + self.fWidth = self.fHeight = width + + # Angle span (travel) + # calf must be 360/36*29=290 + # tube must be 360/14*10=257.14 or 360/12*10=300 + self.fTravel = int(self.getTweak('KnobTravel', travel)) + + # Radius of some notable element of Knob (not exactly the largest) + self.fRadius = int(self.getTweak('KnobRadius', radius)) + + # Size of Button (half of it, similar to "raduis") + self.fSize = int(self.getTweak('ButtonSize', size)) + + # Point, line or other accent on knob + self.fPointSize = point + + # Colouring, either only one or both values can be used for skin. + if (self.subSkin > 0) or (self.skin in (1, 3, 4,)) : + self.fHueA = hueA + self.fHueB = hueB + # default and openav can be re-colored + elif self.colorFollow: + self.fHueA = self.fHueB = int(self.fColorHint) / 100.0 # we use hue only yet else: - self.fImageOrientation = self.VERTICAL + # NOTE: here all incoming color data, except hue, is lost. + self.fHueA = self.fHueB = self.fCustomPaintColor.hueF() - self.updateSizes() - def getBaseSize(self): - return self.fImageBaseSize + metrics = QFontMetrics(self.fLabelFont) - def updateSizes(self): - if isinstance(self.fImage, QPixmap): - self.fImageWidth = self.fImage.width() - self.fImageHeight = self.fImage.height() + if not self.fLabel: + self.fLabelWidth = 0 else: - self.fImageWidth = self.fImage.sizeHint().width() - self.fImageHeight = self.fImage.sizeHint().height() + self.fLabelWidth = fontMetricsHorizontalAdvance(metrics, self.fLabel) - if self.fImageWidth < 1: - self.fImageWidth = 1 + extraWidthAuto = max((self.fLabelWidth - self.fWidth), 0) - if self.fImageHeight < 1: - self.fImageHeight = 1 + self.fLabelHeight = metrics.height() - if self.fImageOrientation == self.HORIZONTAL: - self.fImageBaseSize = self.fImageHeight - self.fImageLayersCount = self.fImageWidth / self.fImageHeight - else: - self.fImageBaseSize = self.fImageWidth - self.fImageLayersCount = self.fImageHeight / self.fImageWidth + if (self.fCustomPaintMode % 16) == 0: # exclude: DryWet, Volume, etc. + extraWidth = int(self.getTweak('GapMin', 0)) + extraWidthLimit = int(self.getTweak('GapMax', 0)) - self.setMinimumSize(self.fImageBaseSize, self.fImageBaseSize + self.fLabelHeight + 5) - self.setMaximumSize(self.fImageBaseSize, self.fImageBaseSize + self.fLabelHeight + 5) + if self.getTweak('GapAuto', 0): + extraWidth = max(extraWidth, extraWidthAuto) + + extraWidth = min(extraWidth, extraWidthLimit) + + self.fWidth = self.fWidth + extraWidth + + self.setMinimumSize(self.fWidth, self.fHeight + self.fLabelHeight + RACK_KNOB_GAP) + self.setMaximumSize(self.fWidth, self.fHeight + self.fLabelHeight + RACK_KNOB_GAP) if not self.fLabel: self.fLabelHeight = 0 - self.fLabelWidth = 0 + # self.fLabelWidth = 0 return - metrics = QFontMetrics(self.fLabelFont) - self.fLabelWidth = fontMetricsHorizontalAdvance(metrics, self.fLabel) - self.fLabelHeight = metrics.height() - - self.fLabelPos.setX(float(self.fImageBaseSize)/2.0 - float(self.fLabelWidth)/2.0) + self.fLabelPos.setX(float(self.fWidth)/2.0 - float(self.fLabelWidth)/2.0) - if self.fImageNum in ("01", "02", "07", "08", "09", "10"): - self.fLabelPos.setY(self.fImageBaseSize + self.fLabelHeight) - elif self.fImageNum in ("11",): - self.fLabelPos.setY(self.fImageBaseSize + self.fLabelHeight*2/3) - else: - self.fLabelPos.setY(self.fImageBaseSize + self.fLabelHeight/2) + # labelLift = (1/2, 1, 2/3, 1/4, 1/2, 1, 1, 1)[skin % 8] + self.fLabelPos.setY(self.fHeight + self.fLabelHeight * labelLift) - self.fLabelGradient.setStart(0, float(self.fImageBaseSize)/2.0) - self.fLabelGradient.setFinalStop(0, self.fImageBaseSize + self.fLabelHeight + 5) + # jpka: TODO Can't see how gradients work, looks like it's never triggered. + self.fLabelGradient.setStart(0, float(self.fHeight)/2.0) + self.fLabelGradient.setFinalStop(0, self.fHeight + self.fLabelHeight + 5) - self.fLabelGradientRect = QRectF(float(self.fImageBaseSize)/8.0, float(self.fImageBaseSize)/2.0, - float(self.fImageBaseSize*3)/4.0, self.fImageBaseSize+self.fLabelHeight+5) + self.fLabelGradientRect = QRectF(float(self.fHeight)/8.0, float(self.fHeight)/2.0, + float(self.fHeight*3)/4.0, self.fHeight+self.fLabelHeight+5) def setImage(self, imageId): - self.fImageNum = "%02i" % imageId - if imageId in (2,6,7,8,9,10,11,12,13): - img = ":/bitmaps/dial_%s%s.png" % (self.fImageNum, "" if self.isEnabled() else "d") - else: - img = ":/scalable/dial_%s%s.svg" % (self.fImageNum, "" if self.isEnabled() else "d") + print("Loopback for self.setupZynFxParams(), FIXME!") + return + + def minimumSizeHint(self): + return QSize(self.fWidth, self.fHeight) - if img.endswith(".png"): - if not isinstance(self.fImage, QPixmap): - self.fImage = QPixmap() + def sizeHint(self): + return QSize(self.fWidth, self.fHeight) + + # def changeEvent(self, event): + # CommonDial.changeEvent(self, event) + # + # # Force svg update if enabled state changes + # if event.type() == QEvent.EnabledChange: + # self.slot_updateImage() + + def drawMark(self, painter, X, Y, r1, r2, angle, width, color): + A = angle * pi/180 + x = X + r1 * cos(A) + y = Y - r1 * sin(A) + painter.setPen(QPen(color, width, cap=Qt.RoundCap)) + if not (r1 == r2): # line + x1 = X + r2 * cos(A) + y1 = Y - r2 * sin(A) + painter.drawLine(QPointF(x, y), QPointF(x1, y1)) + else: # ball + painter.drawEllipse(QRectF(x-width/2, y-width/2, width, width)) + + gradMachined = {5.9, 10.7, 15.7, 20.8, 25.8, 30.6, 40.6, 45.9, + 55.9, 60.7, 65.7, 70.8, 75.8, 80.6, 90.6, 95.9} + + def grayGrad(self, painter, X, Y, a, b, gradPairs, alpha = 1.0): + if b == -1: + grad = QConicalGradient(X, Y, a) + elif b == -2: + grad = QRadialGradient (X, Y, a) else: - if not isinstance(self.fImage, QSvgWidget): - self.fImage = QSvgWidget() + grad = QLinearGradient (X, Y, a, b) - self.fImage.load(img) + for i in gradPairs: + grad.setColorAt(int(i)/100.0, QColor.fromHslF(0, 0, (i % 1.0), alpha)) - if self.fImage.width() > self.fImage.height(): - self.fImageOrientation = self.HORIZONTAL - else: - self.fImageOrientation = self.VERTICAL + return grad - # special svgs - if self.fCustomPaintMode == self.CUSTOM_PAINT_MODE_NULL: - # reserved for carla-wet, carla-vol, carla-pan and color - if self.fImageNum == "03": - self.fCustomPaintMode = self.CUSTOM_PAINT_MODE_COLOR + # Pen is always full opacity (alpha = 1) + def grayGradPen(self, painter, X, Y, a, b, gradPairs = {0.10, 50.30, 100.10}, width = 1.0): + painter.setPen(QPen(self.grayGrad(painter, X, Y, a, b, gradPairs, 1), width, Qt.SolidLine, Qt.FlatCap)) - # reserved for carla-L and carla-R - elif self.fImageNum == "04": - self.fCustomPaintMode = self.CUSTOM_PAINT_MODE_CARLA_L + def grayGradBrush(self, painter, X, Y, a, b, gradPairs, alpha = 1.0): + painter.setBrush(self.grayGrad(painter, X, Y, a, b, gradPairs, alpha)) - # reserved for zita - elif self.fImageNum == "06": - self.fCustomPaintMode = self.CUSTOM_PAINT_MODE_ZITA - self.updateSizes() - self.update() + # Replace Qt draw over substrate bitmap or svg to + # all-in-one widget generated from stratch using Qt only, + # make it highly tuneable, and uniformly look like + # using HSL color model to make same brightness of colored things. + # We can also easily have color tinted (themed) knobs. + # Some things were simplified a little, to gain more speed. + # R: knob nib (cap) radius + def paintDial(self, painter, X, Y, H, S, L, E, normValue, enabled): + R = self.fRadius + barWidth = self.fPointSize + angleSpan = self.fTravel - @pyqtSlot() - def slot_updateImage(self): - self.setImage(int(self.fImageNum)) + hueA = self.fHueA + hueB = self.fHueB - def minimumSizeHint(self): - return QSize(self.fImageBaseSize, self.fImageBaseSize) + color0 = QColor.fromHslF(hueA, S, L, 1) + color0a = QColor.fromHslF(hueA, S, L/2-0.25, 1) + color1 = QColor.fromHslF(hueB, S, L, 1) - def sizeHint(self): - return QSize(self.fImageBaseSize, self.fImageBaseSize) + skin = self.skin - def changeEvent(self, event): - CommonDial.changeEvent(self, event) + def ang(value): + return angleSpan * (0.5 - value) + 90 + + def drawArcV(rect, valFrom, valTo, ticks = 0): + # discretize scale: for 10 points, first will lit at 5%, + # then 15%, and last at 95% of normalized value, + # i.e. treshold is: center of point exactly matches knob mark angle + if ticks: + valTo = int(valTo * (ticks * angleSpan / 360) + 0.5) / (ticks * angleSpan / 360) + + painter.drawArc(rect, int(ang(valFrom) * 16), int((ang(valTo) - ang(valFrom)) * 16)) + + def squareBorder(w): + return QRectF(X-R-w, Y-R-w, (R+w)*2, (R+w)*2) + + def gray(luma): + return QColor.fromHslF(0, 0, luma, 1) + + + # Knob light arc "base" (starting) value/angle. + if self.fCustomPaintMode == self.CUSTOM_PAINT_MODE_CARLA_L: + refValue = 0 + elif self.fCustomPaintMode == self.CUSTOM_PAINT_MODE_CARLA_R: + refValue = 1 + elif (self.fMinimum == -self.fMaximum) and (skin == 0): + refValue = 0.5 + else: + refValue = 0 - # Force svg update if enabled state changes - if event.type() == QEvent.EnabledChange: - self.slot_updateImage() + knobMuted = (self.knobPusheable and (normValue == refValue)) - def paintDial(self, painter): - if self.isEnabled(): - normValue = float(self.fRealValue - self.fMinimum) / float(self.fMaximum - self.fMinimum) - curLayer = int((self.fImageLayersCount - 1) * normValue) + haveLed = self.getTweak('WetVolPushLed', 1) and self.fCustomPaintMode in (1, 2, 5, 6, 9, 10,) - if self.fImageOrientation == self.HORIZONTAL: - xpos = self.fImageBaseSize * curLayer - ypos = 0.0 + if self.fIndex in (PARAMETER_DRYWET, PARAMETER_VOLUME, PARAMETER_PANNING, PARAMETER_FORTH): # -3, -4, -7, -9 + if knobMuted: + if self.fIndex == PARAMETER_DRYWET: + self.pushLabel("Thru") + elif self.fIndex == PARAMETER_VOLUME: + self.pushLabel("Mute") + elif self.fIndex == PARAMETER_PANNING: + self.pushLabel("Center") + else: + self.pushLabel("Midway") + else: + self.popLabel() + + if skin == 0: # mimic svg dial + # if not knobMuted: + if not (knobMuted and haveLed): + # light arc substrate: near black, 0.5 px exposed + painter.setPen(QPen(gray(0.10), barWidth+1, cap=Qt.FlatCap)) + drawArcV(squareBorder(barWidth), 0, 1) + + # light arc: gray bar + # should be combined with light (value) arc to be a bit faster ? + self.grayGradPen(painter, X, Y, 270, -1, {0.20, 100.15}, barWidth) + drawArcV(squareBorder(barWidth), 0, 1) + + # cap + self.grayGradBrush(painter, X-R, Y-R, R*2, -2, {0.45+E, 100.15+E}) + painter.setPen(QPen(gray(0.10), 0.5)) + painter.drawEllipse(squareBorder(1)) + + elif skin == 1: # calf + # outer chamfer & leds substrate + self.grayGradPen(painter, X, Y, 135, -1, {0.15, 50.50, 100.15}, 1.5) + painter.setBrush(color0a) + painter.drawEllipse(squareBorder(barWidth*2-1)) + + # machined shiny cap with chamfer + self.grayGradPen(painter, X, Y, -45, -1, {0.15, 50.50, 100.15}) + self.grayGradBrush(painter, X, Y, 0, -1, self.gradMachined) + painter.drawEllipse(squareBorder(1)) + + elif skin == 2: # openav + # light arc substrate + painter.setPen(QPen(gray(0.20+E), barWidth)) + drawArcV(squareBorder(barWidth), 0, 1) + + elif skin == 3: # zynfx + # light arc substrate + painter.setPen(QPen(QColor.fromHslF(0.57, 0.8, 0.25, 1), barWidth+2, cap=Qt.FlatCap)) + drawArcV(squareBorder(barWidth), 0, 1) + + # cap + painter.setPen(QPen(gray(0.0), 1)) + painter.setBrush(gray(0.3 + E)) + painter.drawEllipse(squareBorder(-2)) + + # These knobs are different for integers and for floats. + elif skin == 4: # tube / bakelite + chamfer = 1.5 # It is best when 1.5 at normal zoom, and 1.0 for >2x HiDpi + # base + self.grayGradPen(painter, X, Y, -45, -1, width=chamfer) + self.grayGradBrush(painter, X-5-ang(normValue)/36, -20, 83, -2, {0.2, 50.2, 51.00, 100.00}) + if self.fIsInteger: # chickenhead knob: small base + painter.drawEllipse(squareBorder(1)) + else: # round knob: larger base + painter.drawEllipse(squareBorder(R*0.7)) + + polygon = QPolygonF() + # "chickenhead" pointer + if self.fIsInteger: + for i in range(17): + A = ((0.01, 0.02, 0.03, 0.06, 0.2, 0.3, 0.44, 0.455, -0.455, -0.44, -0.3, -0.2, -0.06, -0.03, -0.02, -0.01, 0.01)[i] * 360 - ang(normValue)) * pi/180 + r = (1, 0.97, 0.91, 0.7, 0.38, 0.39, 0.87, 0.9, 0.9, 0.87, 0.39, 0.38, 0.7, 0.91, 0.97, 1, 1)[i] * R + polygon.append(QPointF(X + r * 1.75 * cos(A), Y + r * 1.75 * sin(A))) + # 8-teeth round knob outline else: - xpos = 0.0 - ypos = self.fImageBaseSize * curLayer + for i in range(64): + A = (i / 64 * 360 - ang(normValue)) * pi/180 + r = R * (1, 0.95, 0.91, 0.89, 0.88, 0.89, 0.91, 0.95)[i % 8] + polygon.append(QPointF(X + r * 1.5 * cos(A), Y + r * 1.5 * sin(A))) + + self.grayGradPen(painter, X, Y, -45, -1, {0.10, 50.50, 100.10}, chamfer) + self.grayGradBrush(painter, X-5-ang(normValue)/36, -20, 75, -2, {0.2, 50.2, 51.00, 100.00}) + painter.drawPolygon(polygon) + + # machined shiny penny with chamfer + self.grayGradPen(painter, X, Y, 135, -1, {0.15, 50.50, 100.15}) + self.grayGradBrush(painter, X, Y, -ang(normValue)/36, -1, self.gradMachined, 0.75) + if self.fIsInteger: # chickenhead knob: small circle + painter.drawEllipse(squareBorder(-R*0.65)) + else: # round knob: large one + painter.drawEllipse(squareBorder(-1)) + + # Outer scale marks + for i in range(0, 11): + angle = ((0.5-i/10) * angleSpan + 90) + self.drawMark(painter, X, Y, R*2, R*2, angle, barWidth/12 * (4 + 1 * int((i % 10) == 0)), gray(0.5 + E)) + + # if knobMuted: + if (knobMuted and haveLed): + # if self.getTweak('WetVolPushLed', 1): + self.drawMark(painter, X, Y, 0, 0, 0, barWidth, color0) + return - source = QRectF(xpos, ypos, self.fImageBaseSize, self.fImageBaseSize) + # draw arc: forward, or reverse (for 'R' ch knob) + if (not (normValue == refValue)) and (not (skin == 4)): + + gradient = QConicalGradient(X, Y, 270) + cap=Qt.FlatCap + + if not (skin == 1): # any, except calf + ticks = 0 + gradient.setColorAt(0.75, color0) + gradient.setColorAt(0.25, color1) + if skin == 3: # zynfx + # light arc partial (angled) black substrate + painter.setPen(QPen(gray(0.0), barWidth+2, cap=Qt.FlatCap)) + drawArcV(squareBorder(barWidth), refValue-0.013, normValue+0.013) + elif skin == 2: # openav + cap=Qt.RoundCap + else: # calf + ticks = 36 + for i in range(2, ticks-2, 1): + gradient.setColorAt((i+0.5-0.35)/ticks, color0) + gradient.setColorAt((i+0.5) /ticks, Qt.black) + gradient.setColorAt((i+0.5+0.35)/ticks, color0) + + painter.setPen(QPen(gradient, barWidth, Qt.SolidLine, cap)) + drawArcV(QRectF(squareBorder(barWidth)), refValue, normValue, ticks) + + # do not draw marks on disabled items + if not enabled: + return - if isinstance(self.fImage, QPixmap): - target = QRectF(0.0, 0.0, self.fImageBaseSize, self.fImageBaseSize) - painter.drawPixmap(target, self.fImage, source) + A = ang(normValue) + + match skin: + case 0: # ball + self.drawMark(painter, X, Y, R*0.8, R*0.8, A, barWidth/2+0.5, color0) + case 1: # line for calf + self.drawMark(painter, X, Y, R*0.6, R*0.9, A, barWidth/2, Qt.black) + case 2: # line for openav + self.drawMark(painter, X, Y, 0, R+barWidth, A, barWidth, color0) + case 3: # line for zynfx + self.drawMark(painter, X, Y, 2, R-3, A, barWidth/2+0.5, Qt.white) + case 4: # ball + r = R * (int(self.fIsInteger) * 0.25 + 1.2) + self.drawMark(painter, X, Y, r, r, A, barWidth/2+0.5, Qt.white) + + + def paintButton(self, painter, X, Y, H, S, L, E, normValue, enabled): + # W: button cap half-size ; w: bar width + W = self.fRadius + w = self.fPointSize + + hue = self.fHueA + + skin = int(self.fCustomPaintMode / 16) + + def squareBorder(w, dw=0): + return QRectF(X-W-w-dw, Y-W-w, (W+w+dw)*2, (W+w)*2) + + def gray(luma): + return QColor.fromHslF(0, 0, luma, 1) + + color = QColor.fromHslF(hue, S, L, 1) + + centerLed = self.getTweak('ButtonHaveLed', 0) # LED itself & size increase + coloredNeon = self.getTweak('ColoredNeon', 1) # But worse when HighContrast. + + if skin == 0: # internal + if not centerLed: + # light bar substrate: near black, 0.5 px exposed + painter.setPen(QPen(gray(0.10), w+1)) + painter.drawLine(QPointF(X-W/2, Y-W-w), QPointF(X+W/2, Y-W-w)) + + # light bar: gray bar + painter.setPen(QPen(gray(0.20), w)) + painter.drawLine(QPointF(X-W/2, Y-W-w), QPointF(X+W/2, Y-W-w)) + + # cap + self.grayGradBrush(painter, X-W/2, Y-W/2, W*2, -2, {0.13+E, 50.18+E, 100.35+E}) + painter.setPen(QPen(gray(0.05), 1)) + # A bit larger buttons when no top LED, but centered one. + painter.drawRoundedRect(squareBorder(-1+centerLed), 3, 3) + + elif skin == 1: # calf + # outer chamfer & leds substrate + self.grayGradPen(painter, X, Y, 135, -1, {24.25, 26.50, 76.50, 78.25}, 1.5) + painter.setBrush(QColor.fromHslF(hue, S, 0.05+E/2, 1)) + painter.drawRoundedRect(QRectF(X-W-1, Y-W-w-0-1, W*2+2, W*2+w+0+2), 4, 4) + + # machined shiny cap with chamfer + self.grayGradPen(painter, X, Y, -45, -1, {24.25, 26.50, 74.50, 76.25}) + self.grayGradBrush(painter, X, Y, -30, -1, self.gradMachined) + painter.drawRoundedRect(squareBorder(-1), 3, 3) + + elif skin == 2: # openav + # light substrate + pen = QPen(gray(0.20+E), w) + painter.setPen(pen) + painter.drawRoundedRect(squareBorder(0), 3, 3) + + elif skin == 3: # zynfx + if not centerLed: + # light bar substrate: teal, 1 px exposed + painter.setPen(QPen(QColor.fromHslF(hue, 0.8, 0.25, 1), w+2)) + painter.drawLine(QPointF(X-W/2, Y-W-w), QPointF(X+W/2, Y-W-w)) + + # button + painter.setPen(QPen(gray(0.0), 1)) + painter.setBrush(gray(0.3 + E)) + painter.drawRoundedRect(squareBorder(-2, 4), 3, 3) + + elif skin == 4: # tube + # bakelite cap + self.grayGradPen(painter, X, Y, -45, -1) + self.grayGradBrush(painter, X-10, -40, 120, -2, {0.2, 50.2, 51.00, 100.00}) + painter.drawRoundedRect(squareBorder(W*0.2), 3, 3) + + # neon lamp + if (normValue > 0): + grad = QRadialGradient(X, Y, 10) + for i in ({0.6, 20.6, 70.4, 100.0}): + if coloredNeon: + grad.setColorAt(int(i)/100.0, QColor.fromHslF((0.05 - normValue) % 1.0, S, (i % 1.0), 1)) + else: + grad.setColorAt(int(i)/100.0, QColor.fromHslF(0.05, S, (i % 1.0) * normValue, 1)) + + painter.setPen(QPen(Qt.NoPen)) + painter.setBrush(grad) + painter.drawRoundedRect(squareBorder(-W*0.4), 1.5, 1.5) + + # glass over neon lamp + self.grayGradPen(painter, X, Y, 135, -1) + self.grayGradBrush(painter, X-10, -40, 124, -2, {0.9, 50.9, 51.4, 100.4}, 0.25) + painter.drawRoundedRect(squareBorder(-W*0.4), 1.5, 1.5) + + # draw active lights + if skin == 0: # internal + if not centerLed: + if (normValue > 0): + painter.setPen(QPen(color, w)) + if (normValue < 1): + painter.drawLine(QPointF(X-W/2, Y-W-w), QPointF(X-w/2, Y-W-w)) + else: + painter.drawLine(QPointF(X-W/2, Y-W-w), QPointF(X+W/2, Y-W-w)) else: - self.fImage.renderer().render(painter, source) - - # Custom knobs (Dry/Wet and Volume) - if self.fCustomPaintMode in (self.CUSTOM_PAINT_MODE_CARLA_WET, self.CUSTOM_PAINT_MODE_CARLA_VOL): - # knob color - colorGreen = QColor(0x5D, 0xE7, 0x3D).lighter(100 + self.fHoverStep*6) - colorBlue = QColor(0x3E, 0xB8, 0xBE).lighter(100 + self.fHoverStep*6) - - # draw small circle - ballRect = QRectF(8.0, 8.0, 15.0, 15.0) - ballPath = QPainterPath() - ballPath.addEllipse(ballRect) - #painter.drawRect(ballRect) - tmpValue = (0.375 + 0.75*normValue) - ballValue = tmpValue - floor(tmpValue) - ballPoint = ballPath.pointAtPercent(ballValue) - - # draw arc - startAngle = 218*16 - spanAngle = -255*16*normValue - - if self.fCustomPaintMode == self.CUSTOM_PAINT_MODE_CARLA_WET: - painter.setBrush(colorBlue) - painter.setPen(QPen(colorBlue, 0)) - painter.drawEllipse(QRectF(ballPoint.x(), ballPoint.y(), 2.2, 2.2)) - - gradient = QConicalGradient(15.5, 15.5, -45) - gradient.setColorAt(0.0, colorBlue) - gradient.setColorAt(0.125, colorBlue) - gradient.setColorAt(0.625, colorGreen) - gradient.setColorAt(0.75, colorGreen) - gradient.setColorAt(0.76, colorGreen) - gradient.setColorAt(1.0, colorGreen) - painter.setBrush(gradient) - painter.setPen(QPen(gradient, 3)) + painter.setPen(QPen(gray(0.05), 0.5)) + painter.setBrush(color.darker(90 + int(300*(1-normValue)))) + painter.drawRoundedRect(squareBorder(w-W), 1, 1) + + elif skin == 1: # calf + if (normValue > 0): + grad = QLinearGradient(X-W, Y, X+W, Y) + for i in ({20.0, 45.6, 55.6, 80.0} if (normValue < 1) + else {0.0, 30.6, 40.5, 45.7, 55.7, 60.5, 70.6, 100.0}): + grad.setColorAt(int(i)/100.0, QColor.fromHslF(hue, S, (i % 1)+E, 1)) + painter.setPen(QPen(grad, w-0.5, cap=Qt.FlatCap)) + painter.drawLine(QPointF(X-W, Y-W-w/2), QPointF(X+W, Y-W-w/2)) + + elif skin == 2: # openav + painter.setPen(QPen(color, w, cap=Qt.RoundCap)) + if (normValue > 0): + painter.drawRoundedRect(squareBorder(-W * (1 - normValue)), 3, 3) + else: + painter.drawLine(QPointF(X-0.1, Y), QPointF(X+0.1, Y)) + + elif skin == 3: # zynfx + if not centerLed: + if (normValue > 0): + dx = (W - w) if (normValue < 1) else 0 + painter.setPen(QPen(gray(0), w+2)) + painter.drawLine(QPointF(X-W/2, Y-W-w), QPointF(X+W/2-dx, Y-W-w)) + painter.setPen(QPen(color, w)) + painter.drawLine(QPointF(X-W/2, Y-W-w), QPointF(X+W/2-dx, Y-W-w)) + else: + painter.setPen(QPen(gray(0), 1)) + painter.setBrush(color.darker(90 + int(300*(1-normValue)))) + painter.drawEllipse(squareBorder(w-W+1)) + # do not draw marks on disabled items + if not enabled: + return + + match skin: + case 0: # internal: ball at center + if not centerLed: + self.drawMark(painter, X, Y, 0, 0, 0, w/2+0.5, color) + # case 3: # openav + # painter.setPen(QPen(color, w, cap=Qt.RoundCap)) + # painter.drawLine(QPointF(X-0.1, Y), QPointF(X+0.1, Y)) + case 3: # zynfx: ball at center + if not centerLed: + self.drawMark(painter, X, Y, 0, 0, 0, w/2, gray(1)) + + + # Just a text label not so good for fast updated display, see issue #1934. + # NOTE Work in progress. + def paintDisplay(self, painter, X, Y, H, S, L, E, normValue, enabled): + + # X, Y: Center of label. + def plotStr(self, painter, X, Y, st, fontSize, aspectRatio, skew): + + # Due to CPU/speed gain, we use simplest possible 7-segmented digits. + # Shape to Speed balance: Speed + h = ["KYNKNY ROZUZ RVKVY ROJUJ", # 0 NJ RJ VJ + "KYVJVQ RVSVZ", # 1 NR RR VR + "KYNJVJVRNRNZVZ", # 2 NZ RZ VZ + "KYNJVJVZNZ RVRNR", # 3 P] + "KYNJNRVR RVJVZ", # 4 + "KYVJNJNRVRVZNZ", # 5 + "KYVJNJNZVZVRNR", # 6 + "KYNJVJVZ", # 7 + "KYNJNZVZVJNJ RNRVR", # 8 + "KYNZVZVJNJNRVR", # 9 + "KYNRVR", # - + "OTRZP]", # . + "KYNNNX RVPNTVX", # k + "KYNXNLRRVLVX" ] # M + + def plotHersheyChar(painter, X, Y, c, fontSize, aspectRatio, skew, justGetWidth): + lm = (ord(h[c][0]) - ord('R')) * fontSize * aspectRatio + rm = (ord(h[c][1]) - ord('R')) * fontSize * aspectRatio + if justGetWidth: + return X + rm - lm + + points = [] + X = X - lm + # The speed and CPU load is critical here. + # I try to make it as efficient as possible, but can it be even faster? + for i in range(1, int(len(h[c])/2)): + a = (h[c][i*2]) + b = (h[c][i*2+1]) + if (a == ' ') and (b == 'R'): + painter.drawPolyline(points) + points = [] + else: + y = (ord(b) - ord('R')) * fontSize + x = (ord(a) - ord('R')) * fontSize * aspectRatio + skew * y + points.append(QPointF(X+x, Y+y)) + + painter.drawPolyline(points) + X = X + rm + return X + + def plotDecodedChar(painter, X, Y, st, fontSize, aspectRatio, skew, justGetWidth): + for i in range(len(st)): + digit = "0123456789-.kM".find(st[i]) + if digit < 0: + print("ERROR: Illegal char at " + str(i) + " in " + st) + else: + X = plotHersheyChar(painter, X, Y, digit, fontSize, aspectRatio, skew, justGetWidth) + return X + + widthPx = plotDecodedChar(painter, 0, Y, st, fontSize, aspectRatio, skew, 1) + plotDecodedChar(painter, X-widthPx/2, Y, st, fontSize, aspectRatio, skew, 0) + return + + def strLimDigits(x): + s = str(x) + ret = lambda x: float(x) if '.' in s else int(x) + return str(ret(s[:max(s.find('.'), 4+1)].strip('.'))) +# return str(ret(s[:max(s.find('.'), num+1 + ('-' in s))].strip('.'))) + + def plotNixie(n): + painter.setPen(QPen(QColor.fromHslF(0.05, S, L, 1), 2.5, cap=Qt.RoundCap)) + + # We use true arcs instead of polyline/Bezier. + # Arcs are perfectly matched with original tube. + # x = 0..20, y = 0..32 + digits = [[[ 2,00,18,16, 480,2400],[ 00,-8,48,40,2400,3360], + [ 2,16,18,32,3360,5280],[ -28,-8,20,40,5280,6240]], # 0 + [[10,00,10,32, 0, 0]], # 1 + [[ 1,-0.5,19,17.5,4608,8640],[1,17,30,46,1680,2880], + [1,31.5,19,31.5, 0, 0]], # 2 + [[-2,10,20,32,3500,7300],[ 2,00,19,00, 0, 0], + [19,00, 8,10, 0, 0]], # 3 + [[ 1,22,17,00, 0, 0],[ 17,00,17,32, 0, 0], + [1,22,17,22, 0, 0]], # 4 + [[-1,12,19,32,3500,7920],[ 4,00,18,00, 0, 0], + [4,00, 2,14, 0, 0]], # 5 + [[00,12,20,32, 0,5760],[ 0,-10,64,54,2150,2880]], # 6 + [[ 1,00,19,00, 0, 0],[ 19,00, 8,32, 0, 0]], # 7 + [[ 1,14,19,32, 0,5760],[ 3,00,17,14, 0,5760]], # 8 + [[00,00,20,20, 0,5760],[ 20,42,-44,-22,5030,5760]]] # 9 + + for x0, y0, x1, y1, a0, a1 in digits[n]: + if a0 == a1 == 0: + painter.drawLine(QPointF(x0+X-10, y0+Y-16), QPointF(x1+X-10, y1+Y-16)) else: - painter.setBrush(colorBlue) - painter.setPen(QPen(colorBlue, 0)) - painter.drawEllipse(QRectF(ballPoint.x(), ballPoint.y(), 2.2, 2.2)) - - painter.setBrush(colorBlue) - painter.setPen(QPen(colorBlue, 3)) - - painter.drawArc(QRectF(4.0, 4.0, 26.0, 26.0), int(startAngle), int(spanAngle)) - - # Custom knobs (L and R) - elif self.fCustomPaintMode in (self.CUSTOM_PAINT_MODE_CARLA_L, self.CUSTOM_PAINT_MODE_CARLA_R): - # knob color - color = QColor(0xAD, 0xD5, 0x48).lighter(100 + self.fHoverStep*6) - - # draw small circle - ballRect = QRectF(7.0, 8.0, 11.0, 12.0) - ballPath = QPainterPath() - ballPath.addEllipse(ballRect) - #painter.drawRect(ballRect) - tmpValue = (0.375 + 0.75*normValue) - ballValue = tmpValue - floor(tmpValue) - ballPoint = ballPath.pointAtPercent(ballValue) - - painter.setBrush(color) - painter.setPen(QPen(color, 0)) - painter.drawEllipse(QRectF(ballPoint.x(), ballPoint.y(), 2.0, 2.0)) - - # draw arc - if self.fCustomPaintMode == self.CUSTOM_PAINT_MODE_CARLA_L: - startAngle = 218*16 - spanAngle = -255*16*normValue - else: - startAngle = 322.0*16 - spanAngle = 255.0*16*(1.0-normValue) - - painter.setPen(QPen(color, 2.5)) - painter.drawArc(QRectF(3.5, 3.5, 22.0, 22.0), int(startAngle), int(spanAngle)) - - # Custom knobs (Color) - elif self.fCustomPaintMode == self.CUSTOM_PAINT_MODE_COLOR: - # knob color - color = self.fCustomPaintColor.lighter(100 + self.fHoverStep*6) - - # draw small circle - ballRect = QRectF(8.0, 8.0, 15.0, 15.0) - ballPath = QPainterPath() - ballPath.addEllipse(ballRect) - tmpValue = (0.375 + 0.75*normValue) - ballValue = tmpValue - floor(tmpValue) - ballPoint = ballPath.pointAtPercent(ballValue) - - # draw arc - startAngle = 218*16 - spanAngle = -255*16*normValue - - painter.setBrush(color) - painter.setPen(QPen(color, 0)) - painter.drawEllipse(QRectF(ballPoint.x(), ballPoint.y(), 2.2, 2.2)) - - painter.setBrush(color) - painter.setPen(QPen(color, 3)) - painter.drawArc(QRectF(4.0, 4.8, 26.0, 26.0), int(startAngle), int(spanAngle)) - - # Custom knobs (Zita) - elif self.fCustomPaintMode == self.CUSTOM_PAINT_MODE_ZITA: - a = normValue * pi * 1.5 - 2.35 - r = 10.0 - x = 10.5 - y = 10.5 - x += r * sin(a) - y -= r * cos(a) - painter.setBrush(Qt.black) - painter.setPen(QPen(Qt.black, 2)) - painter.drawLine(QPointF(11.0, 11.0), QPointF(x, y)) - - # Custom knobs + rect = QRectF(x0+X-10, y0+Y-16, x1-x0, y1-y0) + painter.drawArc(rect, a0, a1-a0) + + def squareBorder(w, dw=0): + return QRectF(X-W-w-dw, Y-W-w, (W+w+dw)*2, (W+w)*2) + + W = Y-1 # "radius" + value = self.fRealValue + hue = self.fHueA + + # if self.fIsButton: # TODO make it separate paintLED + if (self.fIsInteger and (self.fMinimum == 0) and (self.fMaximum == 1)): # TODO + # Neon lamp + if (self.fCustomPaintMode == 64): # tube + # bakelite lamp holder + self.grayGradPen(painter, X, Y, -45, -1) + self.grayGradBrush(painter, X-10, -40, 120, -2, {0.2, 50.2, 51.00, 100.00}) + painter.drawRoundedRect(squareBorder(-W*0.45), 3, 3) + + # neon lamp + if (normValue > 0): + grad = QRadialGradient(X, Y, 13) + for i in ({0.6, 20.6, 70.4, 100.0}): + grad.setColorAt(int(i)/100.0, QColor.fromHslF(0.05, 1.0, (i % 1.0) * normValue, 1)) + painter.setPen(QPen(Qt.NoPen)) + painter.setBrush(grad) + painter.drawRoundedRect(squareBorder(-W*0.6), 1.5, 1.5) + + # glass over neon lamp + self.grayGradPen(painter, X, Y, 135, -1) + self.grayGradBrush(painter, X-10, -40, 124, -2, {0.9, 50.9, 51.4, 100.4}, 0.25) + painter.drawRoundedRect(squareBorder(-W*0.6), 1.5, 1.5) + return + + painter.setPen(QPen(QColor.fromHslF(0, 0, 0.3-0.15*normValue+E, 1), 1.5)) + painter.setBrush(QColor(QColor.fromHslF(hue, S, L*(normValue*0.8+0.1), 1))) + painter.drawRoundedRect(squareBorder(-W/2-2), 1.5, 1.5) + return + + if (self.fCustomPaintMode == 64) and \ + (self.fIsInteger and (self.fMinimum >= 0) and (self.fMaximum < 20)): + #Nixie tube for 0..9, or 1 1/2 tubes for 11..19 + Y = Y - 2 + self.grayGradPen(painter, X, Y, 135, -1) + self.grayGradBrush(painter, X-10, -40, 120, -2, {0.2, 50.2, 51.00, 100.00}) + painter.drawRoundedRect(squareBorder(-2, -W*0.2), 3, 3) + + if (value < 10): + plotNixie(int(value % 10)) else: + if (value == 11): + X = X + 8 + plotNixie(1) + else: + X = X + 4 + plotNixie(int(value % 10)) + X = X - 16 + plotNixie(1) + return + + # Will it be analog display, or digital 7-segment scale? + if not self.fIsVuOutput: + unit = "" + if abs(value) >= 10000.0: + value = value / 1000.0 + unit = "k" + if abs(value) >= 10000.0: + value = value / 1000.0 + unit = "M" + # Remove trailing decimal zero and decimal point also. + if (value % 1.0) == 0: + value = int(value) + + valueStr = strLimDigits(value) + unit + valueLen = len(valueStr) + if valueLen == 0: + print("Zero length string from " + str(self.fRealValue) + " value.") return - if self.HOVER_MIN < self.fHoverStep < self.HOVER_MAX: - self.fHoverStep += 1 if self.fIsHovered else -1 - QTimer.singleShot(20, self.update) + autoFontsize = int(self.getTweak('Auto7segSize', 0)) # Full auto + autoFontwidth = int(self.getTweak('Auto7segWidth', 1)) # Width only + + skew = -0.2 + substrate = 1 + dY = 3 # Work in progress here. NOTE + fntSize = 0.9 + fntAspect = 0.5 + bgLuma = 0.05 + R = 10 # Replace to W + width = 4 + lineWidth = 2.0 + + if self.fCustomPaintMode == 0: # default / internal + fntSize = 0.75 + Y = Y - 2 + + elif self.fCustomPaintMode == 16: # calf + autoFontsize = 1 + skew = 0 + dY = 4 + bgLuma = 0.1 + R = 12 + + elif self.fCustomPaintMode == 32: # openav + autoFontsize = 1 + substrate = 0 + R = 13 + lineWidth = 3.0 + + elif self.fCustomPaintMode == 48: # zynfx + fntSize = 0.99 + Y = Y - 2 + dY = 4 + bgLuma = 0.12 + R = 12 + + elif self.fCustomPaintMode == 64: # tube + autoFontsize = 1 + R = 13 + lineWidth = 3.0 + + else: + print("Unknown paint mode "+ str(self.fCustomPaintMode) + " display.") + return + + if not self.fIsVuOutput: + if autoFontwidth and (valueLen < 4): + if autoFontsize: + fntSize = fntSize * 4/3 + fntAspect = 1.0 - (valueLen-1) * 0.2 + + lineWidth = fntSize + 0.5 # Work in progress here. NOTE + + # substrate + if substrate: + substratePen = QPen(QColor.fromHslF(0, 0, 0.4+E, 1), 0.5) + if (self.fCustomPaintMode == 64): # tube + if not self.fIsVuOutput: + self.grayGradPen(painter, X, Y, 135, -1) + self.grayGradBrush(painter, X-10, -80, 200, -2, {0.2, 50.2, 51.00, 100.00}) + painter.drawRoundedRect(squareBorder(-W*0.4, W*0.3), 3, 3) + + else: + if self.fCustomPaintMode == 16: # calf + if self.fIsVuOutput: + dY = 9 + painter.setPen(substratePen) + painter.setBrush(QColor(QColor.fromHslF(0, 0, bgLuma, 1))) + painter.drawRoundedRect(squareBorder(-dY, dY), 3, 3) + + + color = QColor.fromHslF(hue, S, L, 1) + painter.setPen(QPen(color, lineWidth, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)) + if not self.fIsVuOutput: + plotStr(self, painter, X, Y, valueStr, fntSize, fntAspect, skew); + else: + if self.fCustomPaintMode == 16: # calf # Work in progress here. NOTE + for i in range(0, 10+1): + if normValue > ((i+0.5)/11): + painter.drawLine(QPointF(X-15+i*3, Y-R/2), QPointF(X-15+i*3, Y+R/2)) + elif self.fCustomPaintMode == 64: # tube # Work in progress here. NOTE + # Draw Cat eye. + chamfer = 1.5 # It is best when 1.5 at normal zoom, and 1.0 for >2x HiDpi + + # base + self.grayGradPen(painter, X, Y, -45, -1, width=chamfer) + self.grayGradBrush(painter, X-10, -20, 83, -2, {0.2, 50.2, 51.00, 100.00}) + painter.drawEllipse(squareBorder(-W*0.2)) + + # green sectors + rays = 4 # There are 4- or 8-rays (2 or 4 notches) tubes + gradient = QConicalGradient(X, Y, 0) + for i in range(rays): + sign = 1 - (i % 2) * 2 + v = min(normValue, 1) * 1.2 + 0.05 # For output, it can be > max. + a = (i % 2) + v * sign + b = a + 0.02 * sign + if v > 0.99 : + # did you notice overlapping sectors? + gradient.setColorAt((i+a)/rays, color) + gradient.setColorAt((i+b)/rays, color.darker(130)) + else: + b = max(-1, min(1, b)) + gradient.setColorAt((i+a)/rays, color.darker(130)) + gradient.setColorAt((i+b)/rays, color.darker(300)) + + self.grayGradPen(painter, X, Y, 0, -1, width=chamfer) + painter.setBrush(gradient) + painter.drawEllipse(squareBorder(-W*0.4)) + + # cap is black itself, but looks dark green on working tube. + self.drawMark(painter, X, Y, 0, 0, 0, W/4, color.darker(800)) - else: # isEnabled() - target = QRectF(0.0, 0.0, self.fImageBaseSize, self.fImageBaseSize) - if isinstance(self.fImage, QPixmap): - painter.drawPixmap(target, self.fImage, target) else: - self.fImage.renderer().render(painter, target) + # VU scale points + for i in range(0, 5+1): + angle = ((0.5-i/5) * 110 + 90) + self.drawMark(painter, X, Y + R*0.6, R*1.3, R*1.5, angle, lineWidth-0.5, color.darker(150)) + + # VU pointer + angle = ((0.5-normValue) * 110 + 90) + self.drawMark(painter, X, Y + R*0.6, 0, R*1.3, angle, lineWidth, color) + + # Draw "settling screw" of VU meter # Work in progress here. NOTE + if substrate: + painter.setPen(substratePen) + painter.drawEllipse(QRectF(X-width, Y+R*0.6-width, width*2, width*2)) # --------------------------------------------------------------------------------------------------------------------- diff --git a/source/frontend/xycontroller-ui b/source/frontend/xycontroller-ui index 06848b457..67c9803b5 100755 --- a/source/frontend/xycontroller-ui +++ b/source/frontend/xycontroller-ui @@ -10,17 +10,21 @@ from qt_compat import qt_config if qt_config == 5: from PyQt5.QtCore import pyqtSignal, pyqtSlot, QT_VERSION, Qt, QPointF, QRectF, QSize, QTimer from PyQt5.QtGui import QColor, QPainter, QPen - from PyQt5.QtWidgets import QGraphicsScene, QGraphicsSceneMouseEvent, QMainWindow + from PyQt5.QtWidgets import QGraphicsScene, QGraphicsSceneMouseEvent, QMainWindow, QApplication elif qt_config == 6: from PyQt6.QtCore import pyqtSignal, pyqtSlot, QT_VERSION, Qt, QPointF, QRectF, QSize, QTimer from PyQt6.QtGui import QColor, QPainter, QPen - from PyQt6.QtWidgets import QGraphicsScene, QGraphicsSceneMouseEvent, QMainWindow + from PyQt6.QtWidgets import QGraphicsScene, QGraphicsSceneMouseEvent, QMainWindow, QApplication + +import ctypes +from time import sleep # ----------------------------------------------------------------------- # Imports (Custom) from carla_shared import * from carla_utils import * +from widgets.scalabledial import ScalableDial import ui_xycontroller @@ -32,15 +36,21 @@ from externalui import ExternalUI from widgets.paramspinbox import ParamSpinBox # ------------------------------------------------------------------------------------------------------------ - -XYCONTROLLER_PARAMETER_X = 0 -XYCONTROLLER_PARAMETER_Y = 1 +XYCONTROLLER_PARAMETER_SMOOTH = 0 +XYCONTROLLER_PARAMETER_LINEAR = 1 +XYCONTROLLER_PARAMETER_SPEED = 2 +XYCONTROLLER_PARAMETER_REVERSEY = 3 +XYCONTROLLER_PARAMETER_X = 4 +XYCONTROLLER_PARAMETER_Y = 5 +XYCONTROLLER_PARAMETER_OUT_X = 6 +XYCONTROLLER_PARAMETER_OUT_Y = 7 # ------------------------------------------------------------------------------------------------------------ class XYGraphicsScene(QGraphicsScene): # signals cursorMoved = pyqtSignal(float,float) + knobsUpdate = pyqtSignal(float,float) def __init__(self, parent): QGraphicsScene.__init__(self, parent) @@ -52,9 +62,20 @@ class XYGraphicsScene(QGraphicsScene): self.m_channels = [] self.m_mouseLock = False self.m_smooth = False + self.m_linear = False + self.m_speed = 8.0 # 1.0 to 100.0 + self.m_rSmooth = False # Using right button self.m_smooth_x = 0.0 self.m_smooth_y = 0.0 + self.reverseY = 1.0 # -1.0 when reversed + + self.xpPrev = 0.0 + self.ypPrev = 0.0 + + self.time: ctypes.c_uint64 = 0 # I sure just int is quite enough here, but... + self.prevTime: ctypes.c_uint64 = 0 + self.setBackgroundBrush(Qt.black) cursorPen = QPen(QColor(255, 255, 255), 2) @@ -87,31 +108,31 @@ class XYGraphicsScene(QGraphicsScene): self.m_lineV.setX(posX) if forward: - value = posX / (self.p_size.x() + self.p_size.width()); + value = posX / (self.p_size.x() + self.p_size.width()) self.sendMIDI(value, None) else: - self.m_smooth_x = posX; + self.m_smooth_x = posX def setPosY(self, y: float, forward: bool = True): if self.m_mouseLock: - return; + return - posY = y * (self.p_size.y() + self.p_size.height()) + posY = y * (self.p_size.y() + self.p_size.height()) * self.reverseY self.m_cursor.setPos(self.m_cursor.x(), posY) self.m_lineH.setY(posY) if forward: - value = posY / (self.p_size.y() + self.p_size.height()) + value = posY / (self.p_size.y() + self.p_size.height()) * self.reverseY self.sendMIDI(None, value) else: self.m_smooth_y = posY - def setSmooth(self, smooth: bool): - self.m_smooth = smooth - def setSmoothValues(self, x: float, y: float): - self.m_smooth_x = x * (self.p_size.x() + self.p_size.width()); - self.m_smooth_y = y * (self.p_size.y() + self.p_size.height()); + self.m_smooth_x = x * (self.p_size.x() + self.p_size.width()) + self.m_smooth_y = y * (self.p_size.y() + self.p_size.height()) * self.reverseY + + def setReverseY(self, rev: bool): + self.reverseY = 1 - (int(rev) * 2) # 1.0 or -1.0 # ------------------------------------------------------------------- @@ -119,82 +140,132 @@ class XYGraphicsScene(QGraphicsScene): self.p_size.setRect(-(float(size.width())/2), -(float(size.height())/2), size.width(), - size.height()); + size.height()) + + def updatePos(self, pos: QPointF, filterSame: bool = False, knobsOnly: bool = False): + xp = pos.x() / (self.p_size.x() + self.p_size.width()) + yp = pos.y() / (self.p_size.y() + self.p_size.height()) * self.reverseY + if knobsOnly: + self.knobsUpdate.emit(xp * 100, yp * 100) + return + + self.m_cursor.setPos(pos) + self.m_lineH.setY(pos.y()) + self.m_lineV.setX(pos.x()) + + # Set 0.05% precision, yet exact final value settling, esp. zero + xp = round(xp * 1000) / 1000 + yp = round(yp * 1000) / 1000 + + self.sendMIDI(xp, yp, filterSame) + self.cursorMoved.emit(xp, yp) - def updateSmooth(self): - if not self.m_smooth: + def updateSmooth(self, time): + if not (self.m_smooth or self.m_rSmooth): return - if self.m_cursor.x() == self.m_smooth_x and self.m_cursor.y() == self.m_smooth_y: + dx = self.m_smooth_x - self.m_cursor.x() + dy = self.m_smooth_y - self.m_cursor.y() + + if dx == dy == 0: return same = 0 - if abs(self.m_cursor.x() - self.m_smooth_x) <= 0.0005: + if abs(dx) <= 0.0005: self.m_smooth_x = self.m_cursor.x() same += 1 - if abs(self.m_cursor.y() - self.m_smooth_y) <= 0.0005: + if abs(dy) <= 0.0005: self.m_smooth_y = self.m_cursor.y() same += 1 if same == 2: return - newX = float(self.m_smooth_x + self.m_cursor.x()*7) / 8 - newY = float(self.m_smooth_y + self.m_cursor.y()*7) / 8 - pos = QPointF(newX, newY) + speed = self.m_speed - self.m_cursor.setPos(pos) - self.m_lineH.setY(pos.y()) - self.m_lineV.setX(pos.x()) + mod = QApplication.keyboardModifiers() + if (mod & Qt.ControlModifier): + speed /= 2 + elif (mod & Qt.ShiftModifier): + speed *= 2 - xp = pos.x() / (self.p_size.x() + self.p_size.width()) - yp = pos.y() / (self.p_size.y() + self.p_size.height()) + if self.m_linear: + newX = self.m_cursor.x() + max(min(dx / speed, 1), -1) * speed + newY = self.m_cursor.y() + max(min(dy / speed, 1), -1) * speed + else: + precision = 64 / speed + newX = float(self.m_smooth_x + self.m_cursor.x()*(precision-1)) / precision + newY = float(self.m_smooth_y + self.m_cursor.y()*(precision-1)) / precision - self.sendMIDI(xp, yp) - self.cursorMoved.emit(xp, yp) + pos = QPointF(newX, newY) + + self.updatePos(pos, ((time - self.prevTime) == 1)) # Continuous calls or Not + + self.prevTime = time # ------------------------------------------------------------------- - def handleMousePos(self, pos: QPointF): + def handleMousePos(self, event): + + if (event.buttons() & Qt.MiddleButton): + pos = QPointF(0, 0) + else: + pos = QPointF(event.scenePos()) + if not self.p_size.contains(pos): if pos.x() < self.p_size.x(): pos.setX(self.p_size.x()) elif pos.x() > (self.p_size.x() + self.p_size.width()): - pos.setX(self.p_size.x() + self.p_size.width()); + pos.setX(self.p_size.x() + self.p_size.width()) if pos.y() < self.p_size.y(): pos.setY(self.p_size.y()) elif pos.y() > (self.p_size.y() + self.p_size.height()): pos.setY(self.p_size.y() + self.p_size.height()) + self.updatePos(pos, knobsOnly=True) + self.m_smooth_x = pos.x() self.m_smooth_y = pos.y() - if not self.m_smooth: - self.m_cursor.setPos(pos) - self.m_lineH.setY(pos.y()) - self.m_lineV.setX(pos.x()) + self.m_rSmooth = event.buttons() & Qt.RightButton - xp = pos.x() / (self.p_size.x() + self.p_size.width()); - yp = pos.y() / (self.p_size.y() + self.p_size.height()); + # When not smooth, update each time; + # (commented-out part) When smooth, update (re-send same) if click position is same (when smoothing not sends anything). (To match non-smoothed behaviour) + if (not (self.m_smooth or self.m_rSmooth)): # or (abs(self.m_cursor.x() - self.m_smooth_x) <= 0.0005 and abs(self.m_cursor.y() - self.m_smooth_y) <= 0.0005): + self.updatePos(pos) - self.sendMIDI(xp, yp) - self.cursorMoved.emit(xp, yp) - - def sendMIDI(self, xp, yp): + def sendMIDI(self, xp, yp, filterSame = False): rate = float(0xff) / 4 - msgd = ["cc2" if xp is not None and yp is not None else "cc"] + prefix = [] + msgd = [] if xp is not None: - msgd.append(self.cc_x) - msgd.append(int(xp * rate + rate)) + value = int(xp * rate + rate) + if not (filterSame and (value == self.xpPrev)): + prefix = ["cc"] + + msgd.append(self.cc_x) + msgd.append(value) + + self.xpPrev = value if yp is not None: - msgd.append(self.cc_y) - msgd.append(int(yp * rate + rate)) + value = int(yp * rate + rate) + if not (filterSame and (value == self.ypPrev)): + if prefix == []: + prefix = ["cc"] + else: + prefix = ["cc2"] + + msgd.append(self.cc_y) + msgd.append(value) - self.rparent.send(msgd) + self.ypPrev = value + + if not (prefix == []): + self.rparent.send(prefix + msgd) # ------------------------------------------------------------------- @@ -206,13 +277,13 @@ class XYGraphicsScene(QGraphicsScene): def mousePressEvent(self, event: QGraphicsSceneMouseEvent): self.m_mouseLock = True - self.handleMousePos(event.scenePos()) + self.handleMousePos(event) self.rparent.setCursor(Qt.CrossCursor) - QGraphicsScene.mousePressEvent(self, event); + QGraphicsScene.mousePressEvent(self, event) def mouseMoveEvent(self, event: QGraphicsSceneMouseEvent): - self.handleMousePos(event.scenePos()) - QGraphicsScene.mouseMoveEvent(self, event); + self.handleMousePos(event) + QGraphicsScene.mouseMoveEvent(self, event) def mouseReleaseEvent(self, event: QGraphicsSceneMouseEvent): self.m_mouseLock = False @@ -231,15 +302,22 @@ class XYControllerUI(ExternalUI, QMainWindow): self.fSaveSizeNowChecker = -1 + self.isXActual = False + self.isYActual = False + # --------------------------------------------------------------- # Set-up GUI stuff self.scene = XYGraphicsScene(self) - self.ui.dial_x.setImage(2) - self.ui.dial_y.setImage(2) - self.ui.dial_x.setLabel("X") - self.ui.dial_y.setLabel("Y") + # Now knobs are QWidgets, not QDials. + self.ui.dial_x = ScalableDial(self.ui.dial_x, 0, 400, 0, -100, 100, "X", 64, -1, "%", "tube", 1, {}) + self.ui.dial_y = ScalableDial(self.ui.dial_y, 1, 400, 0, -100, 100, "Y", 64, -1, "%", "tube", 1, {}) + + # These are outputs (7-seg displays). + self.ui.dial_out_x = ScalableDial(self.ui.dial_out_x, 2, 400, 0, -100, 100, "Out X", 64, -1, "%", "tube", 1, {'Auto7segWidth':1, }, isOutput=True) + self.ui.dial_out_y = ScalableDial(self.ui.dial_out_y, 3, 400, 0, -100, 100, "Out Y", 64, -1, "%", "tube", 1, {'Auto7segWidth':1, }, isOutput=True) + self.ui.keyboard.setOctaves(10) self.ui.graphicsView.setScene(self.scene) @@ -262,6 +340,7 @@ class XYControllerUI(ExternalUI, QMainWindow): # Connect actions to functions self.scene.cursorMoved.connect(self.slot_sceneCursorMoved) + self.scene.knobsUpdate.connect(self.slot_setKnobs) self.ui.keyboard.noteOn.connect(self.slot_noteOn) self.ui.keyboard.noteOff.connect(self.slot_noteOff) @@ -271,6 +350,7 @@ class XYControllerUI(ExternalUI, QMainWindow): self.ui.dial_x.realValueChanged.connect(self.slot_knobValueChangedX) self.ui.dial_y.realValueChanged.connect(self.slot_knobValueChangedY) + if QT_VERSION >= 0x60000: self.ui.cb_control_x.currentTextChanged.connect(self.slot_checkCC_X) self.ui.cb_control_y.currentTextChanged.connect(self.slot_checkCC_Y) @@ -308,17 +388,23 @@ class XYControllerUI(ExternalUI, QMainWindow): # ------------------------------------------------------------------- + def setSmooth(self, smooth: bool): + self.scene.m_smooth = smooth + + x = self.ui.dial_x.rvalue() / 100 + y = self.ui.dial_y.rvalue() / 100 + if smooth: + self.scene.setSmoothValues(x, y) + else: + self.scene.setPosX(x, True) + self.scene.setPosY(y, True) + self.slot_sceneCursorMoved(x, y) + @pyqtSlot() def slot_updateScreen(self): self.ui.graphicsView.centerOn(0, 0) self.scene.updateSize(self.ui.graphicsView.size()) - dial_x = self.ui.dial_x.rvalue() - dial_y = self.ui.dial_y.rvalue() - self.scene.setPosX(dial_x / 100, False) - self.scene.setPosY(dial_y / 100, False) - self.scene.setSmoothValues(dial_x / 100, dial_y / 100) - @pyqtSlot(int) def slot_noteOn(self, note): self.send(["note", True, note]) @@ -328,16 +414,34 @@ class XYControllerUI(ExternalUI, QMainWindow): self.send(["note", False, note]) @pyqtSlot(float) - def slot_knobValueChangedX(self, x: float): - self.sendControl(XYCONTROLLER_PARAMETER_X, x) - self.scene.setPosX(x / 100, True) - self.scene.setSmoothValues(x / 100, self.ui.dial_y.rvalue() / 100) + def slot_knobValueChangedX(self, x:float, external:bool=False, firstRun:bool=False): + if not external: + self.sendControl(XYCONTROLLER_PARAMETER_X, x) + else: + self.ui.dial_x.setValue(x, False) + + if (not self.scene.m_smooth) or firstRun: + self.sendControl(XYCONTROLLER_PARAMETER_OUT_X, x) + self.ui.dial_out_x.setValue(x, False) + self.scene.setPosX(x / 100, True) + + else: + self.scene.setSmoothValues(x / 100, self.ui.dial_y.rvalue() / 100) @pyqtSlot(float) - def slot_knobValueChangedY(self, y: float): - self.sendControl(XYCONTROLLER_PARAMETER_Y, y) - self.scene.setPosY(y / 100, True) - self.scene.setSmoothValues(self.ui.dial_x.rvalue() / 100, y / 100) + def slot_knobValueChangedY(self, y:float, external:bool=False, firstRun:bool=False): + if not external: + self.sendControl(XYCONTROLLER_PARAMETER_Y, y) + else: + self.ui.dial_y.setValue(y, False) + + if (not self.scene.m_smooth) or firstRun: + self.sendControl(XYCONTROLLER_PARAMETER_OUT_Y, y) + self.ui.dial_out_y.setValue(y, False) + self.scene.setPosY(y / 100, True) + + else: + self.scene.setSmoothValues(self.ui.dial_x.rvalue() / 100, y / 100) @pyqtSlot(str) def slot_checkCC_X(self, text: str): @@ -422,20 +526,16 @@ class XYControllerUI(ExternalUI, QMainWindow): @pyqtSlot(bool) def slot_setSmooth(self, smooth): - self.scene.setSmooth(smooth) + self.setSmooth(smooth) self.sendConfigure("smooth", "yes" if smooth else "no") - - if smooth: - dial_x = self.ui.dial_x.rvalue() - dial_y = self.ui.dial_y.rvalue() - self.scene.setSmoothValues(dial_x / 100, dial_y / 100) + self.sendControl(XYCONTROLLER_PARAMETER_SMOOTH, int(smooth)) @pyqtSlot(float, float) def slot_sceneCursorMoved(self, xp: float, yp: float): - self.ui.dial_x.setValue(xp * 100, False) - self.ui.dial_y.setValue(yp * 100, False) - self.sendControl(XYCONTROLLER_PARAMETER_X, xp * 100) - self.sendControl(XYCONTROLLER_PARAMETER_Y, yp * 100) + self.ui.dial_out_x.setValue(xp * 100, False) + self.ui.dial_out_y.setValue(yp * 100, False) + self.sendControl(XYCONTROLLER_PARAMETER_OUT_X, xp * 100) + self.sendControl(XYCONTROLLER_PARAMETER_OUT_Y, yp * 100) @pyqtSlot(bool) def slot_showKeyboard(self, yesno): @@ -443,21 +543,47 @@ class XYControllerUI(ExternalUI, QMainWindow): self.sendConfigure("show-midi-keyboard", "yes" if yesno else "no") QTimer.singleShot(0, self.slot_updateScreen) + @pyqtSlot(float, float) + def slot_setKnobs(self, x, y): + self.ui.dial_x.setValue(x, False) + self.ui.dial_y.setValue(y, False) + self.sendControl(XYCONTROLLER_PARAMETER_X, x) + self.sendControl(XYCONTROLLER_PARAMETER_Y, y) + # ------------------------------------------------------------------- # DSP Callbacks + # NOTE It called continuously with params 6, 7 (outs, not used here), is this good? def dspParameterChanged(self, index: int, value: float): - if index == XYCONTROLLER_PARAMETER_X: - self.ui.dial_x.setValue(value, False) - self.scene.setPosX(value / 100, False) + if index == XYCONTROLLER_PARAMETER_SMOOTH: + self.ui.cb_smooth.blockSignals(True) + self.ui.cb_smooth.setChecked(bool(value)) + self.ui.cb_smooth.blockSignals(False) + self.setSmooth(bool(value)) + + elif index == XYCONTROLLER_PARAMETER_LINEAR: + self.scene.m_linear = bool(value) + + elif index == XYCONTROLLER_PARAMETER_SPEED: + self.scene.m_speed = value + + elif index == XYCONTROLLER_PARAMETER_REVERSEY: + self.scene.setReverseY(bool(value)) + + elif index == XYCONTROLLER_PARAMETER_X: + self.slot_knobValueChangedX(value, True, not self.isXActual) + self.isXActual = True + elif index == XYCONTROLLER_PARAMETER_Y: - self.ui.dial_y.setValue(value, False) - self.scene.setPosY(value / 100, False) + self.slot_knobValueChangedY(value, True, not self.isYActual) + if self.isXActual and not self.isYActual: + # Run it once, BUT when both X & Y are received (they can be shuffled). + self.scene.setSmoothValues(self.ui.dial_x.rvalue() / 100, self.ui.dial_y.rvalue() / 100) + self.isYActual = True + else: return - self.scene.setSmoothValues(self.ui.dial_x.rvalue() / 100, - self.ui.dial_y.rvalue() / 100) def dspStateChanged(self, key: str, value: str): if key == "guiWidth": @@ -480,15 +606,7 @@ class XYControllerUI(ExternalUI, QMainWindow): elif key == "smooth": smooth = (value == "yes") - self.ui.cb_smooth.blockSignals(True) - self.ui.cb_smooth.setChecked(smooth) - self.ui.cb_smooth.blockSignals(False) - self.scene.setSmooth(smooth) - - if smooth: - dial_x = self.ui.dial_x.rvalue() - dial_y = self.ui.dial_y.rvalue() - self.scene.setSmoothValues(dial_x / 100, dial_y / 100) + self.setSmooth(bool(smooth)) elif key == "show-midi-keyboard": show = (value == "yes") @@ -587,9 +705,10 @@ class XYControllerUI(ExternalUI, QMainWindow): QMainWindow.resizeEvent(self, event) def timerEvent(self, event): + self.scene.time += 1 if event.timerId() == self.fIdleTimer: self.idleExternalUI() - self.scene.updateSmooth() + self.scene.updateSmooth(self.scene.time) if self.fSaveSizeNowChecker == 11: self.sendConfigure("guiWidth", str(self.width())) diff --git a/source/native-plugins/xycontroller.cpp b/source/native-plugins/xycontroller.cpp index 5b90a9465..27f90b2e7 100644 --- a/source/native-plugins/xycontroller.cpp +++ b/source/native-plugins/xycontroller.cpp @@ -29,6 +29,10 @@ class XYControllerPlugin : public NativePluginAndUiClass { public: enum Parameters { + kParamSmooth, + kParamLinear, + kParamSpeed, + kParamReverseY, kParamInX, kParamInY, kParamOutX, @@ -44,6 +48,7 @@ public: mqueueRT() { carla_zeroStruct(params); + params[kParamSpeed] = 8.0f; carla_zeroStruct(channels); channels[0] = true; } @@ -92,6 +97,56 @@ protected: hints |= NATIVE_PARAMETER_IS_OUTPUT; param.name = "Out Y"; break; + + case kParamSpeed: + param.name = "Speed"; + param.unit = "px"; + param.ranges.def = 8.0f; + param.ranges.min = 1.0f; + break; + } + + if (param.name == nullptr) + { + hints |= NATIVE_PARAMETER_IS_INTEGER | NATIVE_PARAMETER_USES_SCALEPOINTS; + param.unit = nullptr; + param.ranges.min = 0; + param.ranges.max = 1; + param.scalePointCount = 2; + + switch (index) + { + case kParamSmooth: + param.name = "Smooth"; + { + static const NativeParameterScalePoint scalePoints[2] = { + { "Thru", 0 }, + { "Smooth", 1 } + }; + param.scalePoints = scalePoints; + } + break; + case kParamLinear: + param.name = "Linear"; + { + static const NativeParameterScalePoint scalePoints[2] = { + { "Log", 0 }, + { "Linear", 1 } + }; + param.scalePoints = scalePoints; + } + break; + case kParamReverseY: + param.name = "Rev Y"; + { + static const NativeParameterScalePoint scalePoints[2] = { + { "Top to Bottom", 0 }, + { "Bottom to Top", 1 } + }; + param.scalePoints = scalePoints; + } + break; + } } param.hints = static_cast(hints); @@ -113,8 +168,14 @@ protected: { switch (index) { + case kParamSmooth: + case kParamLinear: + case kParamSpeed: + case kParamReverseY: case kParamInX: case kParamInY: + case kParamOutX: + case kParamOutY: params[index] = value; break; } @@ -147,8 +208,8 @@ protected: void process(const float* const*, float**, const uint32_t, const NativeMidiEvent* const midiEvents, const uint32_t midiEventCount) override { - params[kParamOutX] = params[kParamInX]; - params[kParamOutY] = params[kParamInY]; + // params[kParamOutX] = params[kParamInX]; + // params[kParamOutY] = params[kParamInY]; if (mqueue.isNotEmpty() && mqueueRT.tryToCopyDataFrom(mqueue)) { @@ -266,7 +327,7 @@ static const NativePluginDescriptor notesDesc = { /* audioOuts */ 0, /* midiIns */ 1, /* midiOuts */ 1, - /* paramIns */ 2, + /* paramIns */ 6, /* paramOuts */ 2, /* name */ "XY Controller", /* label */ "xycontroller", diff --git a/source/rest/carla-host.cpp b/source/rest/carla-host.cpp index d22c7d49a..7c88e37c2 100644 --- a/source/rest/carla-host.cpp +++ b/source/rest/carla-host.cpp @@ -994,6 +994,20 @@ void handle_carla_set_panning(const std::shared_ptr session) session->close(OK); } +void handle_carla_set_forth(const std::shared_ptr session) +{ + const std::shared_ptr request = session->get_request(); + + const int pluginId = std::atoi(request->get_query_parameter("pluginId").c_str()); + CARLA_SAFE_ASSERT_RETURN(pluginId >= 0,) + + const double value = std::atof(request->get_query_parameter("value").c_str()); + CARLA_SAFE_ASSERT_RETURN(value >= -1.0 && value <= 1.0,) + + carla_set_forth(pluginId, value); + session->close(OK); +} + void handle_carla_set_ctrl_channel(const std::shared_ptr session) { const std::shared_ptr request = session->get_request(); diff --git a/source/rest/rest-server.cpp b/source/rest/rest-server.cpp index 8778bfc96..1773e4a39 100644 --- a/source/rest/rest-server.cpp +++ b/source/rest/rest-server.cpp @@ -407,6 +407,7 @@ int main(int, const char**) make_resource(service, "/set_balance_left", handle_carla_set_balance_left); make_resource(service, "/set_balance_right", handle_carla_set_balance_right); make_resource(service, "/set_panning", handle_carla_set_panning); + make_resource(service, "/set_forth", handle_carla_set_forth); make_resource(service, "/set_ctrl_channel", handle_carla_set_ctrl_channel); make_resource(service, "/set_option", handle_carla_set_option); diff --git a/source/utils/CarlaBackendUtils.hpp b/source/utils/CarlaBackendUtils.hpp index 59d0c3325..08a5b8322 100644 --- a/source/utils/CarlaBackendUtils.hpp +++ b/source/utils/CarlaBackendUtils.hpp @@ -196,6 +196,8 @@ const char* InternalParameterIndex2Str(const InternalParameterIndex index) noexc return "PARAMETER_BALANCE_RIGHT"; case PARAMETER_PANNING: return "PARAMETER_PANNING"; + case PARAMETER_FORTH: + return "PARAMETER_FORTH"; case PARAMETER_CTRL_CHANNEL: return "PARAMETER_CTRL_CHANNEL"; #endif diff --git a/source/utils/CarlaStateUtils.cpp b/source/utils/CarlaStateUtils.cpp index 39b98cfaa..ea0cc86d4 100644 --- a/source/utils/CarlaStateUtils.cpp +++ b/source/utils/CarlaStateUtils.cpp @@ -185,6 +185,7 @@ CarlaStateSave::CarlaStateSave() noexcept balanceLeft(-1.0f), balanceRight(1.0f), panning(0.0f), + forth(0.0f), ctrlChannel(-1), #endif currentProgramIndex(-1), @@ -243,6 +244,7 @@ void CarlaStateSave::clear() noexcept balanceLeft = -1.0f; balanceRight = 1.0f; panning = 0.0f; + forth = 0.0f; ctrlChannel = -1; #endif @@ -346,6 +348,10 @@ bool CarlaStateSave::fillFromXmlElement(const XmlElement* const xmlElement) { panning = carla_fixedValue(-1.0f, 1.0f, text.getFloatValue()); } + else if (tag == "Forth") + { + forth = carla_fixedValue(-1.0f, 1.0f, text.getFloatValue()); + } else if (tag == "ControlChannel") { if (! text.startsWithIgnoreCase("n")) @@ -627,6 +633,8 @@ void CarlaStateSave::dumpToMemoryStream(MemoryOutputStream& content) const dataXml << " " << water::String(balanceRight, 7) << "\n"; if (carla_isNotEqual(panning, 0.0f)) dataXml << " " << water::String(panning, 7) << "\n"; + if (carla_isNotEqual(forth, 0.0f)) + dataXml << " " << water::String(forth, 7) << "\n"; if (ctrlChannel < 0) dataXml << " N\n"; diff --git a/source/utils/CarlaStateUtils.hpp b/source/utils/CarlaStateUtils.hpp index a420635ac..a55ca7580 100644 --- a/source/utils/CarlaStateUtils.hpp +++ b/source/utils/CarlaStateUtils.hpp @@ -69,6 +69,7 @@ struct CarlaStateSave { float balanceLeft; float balanceRight; float panning; + float forth; int8_t ctrlChannel; #endif From e5ab114eef0573002072593460d30d6f88353362 Mon Sep 17 00:00:00 2001 From: falkTX Date: Sun, 27 Jul 2025 14:33:49 +0200 Subject: [PATCH 2/7] Remove numpy dependency Signed-off-by: falkTX --- source/frontend/carla_shared.py | 9 +++++++-- source/frontend/carla_widgets.py | 1 - source/frontend/widgets/commondial.py | 3 +-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/source/frontend/carla_shared.py b/source/frontend/carla_shared.py index 851e652c5..e93925b92 100644 --- a/source/frontend/carla_shared.py +++ b/source/frontend/carla_shared.py @@ -9,7 +9,6 @@ import os import sys from math import fmod, log10 -import numpy as np # ------------------------------------------------------------------------------------------------------------ # Imports (Signal) @@ -965,7 +964,13 @@ def getPrefixSuffix(unit): # ------------------------------------------------------------------------------------------------------------ def strLim(value, digits = 5): - result = np.format_float_positional(value, trim='-', fractional=False, precision=digits) + # 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: diff --git a/source/frontend/carla_widgets.py b/source/frontend/carla_widgets.py index 82a00a4f4..9fddbb6aa 100755 --- a/source/frontend/carla_widgets.py +++ b/source/frontend/carla_widgets.py @@ -6,7 +6,6 @@ # Imports (Global) from abc import abstractmethod -import numpy as np # ------------------------------------------------------------------------------------------------------------ # Imports (PyQt) diff --git a/source/frontend/widgets/commondial.py b/source/frontend/widgets/commondial.py index 2ac835e22..b6d3c4010 100644 --- a/source/frontend/widgets/commondial.py +++ b/source/frontend/widgets/commondial.py @@ -1,12 +1,11 @@ #!/usr/bin/env python3 -# SPDX-FileCopyrightText: 2011-2024 Filipe Coelho +# SPDX-FileCopyrightText: 2011-2025 Filipe Coelho # SPDX-License-Identifier: GPL-2.0-or-later # --------------------------------------------------------------------------------------------------------------------- # Imports (Global) from math import isnan, log10 -import numpy as np from qt_compat import qt_config From 08a28b71cc0800641a26f4ab1674baef3f80f30d Mon Sep 17 00:00:00 2001 From: falkTX Date: Sun, 27 Jul 2025 14:46:01 +0200 Subject: [PATCH 3/7] Fix build Signed-off-by: falkTX --- source/backend/plugin/CarlaPluginFluidSynth.cpp | 2 +- source/backend/plugin/CarlaPluginLADSPADSSI.cpp | 4 ++-- source/backend/plugin/CarlaPluginLV2.cpp | 4 ++-- source/backend/plugin/CarlaPluginNative.cpp | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/source/backend/plugin/CarlaPluginFluidSynth.cpp b/source/backend/plugin/CarlaPluginFluidSynth.cpp index 95bfe3179..31124eec3 100644 --- a/source/backend/plugin/CarlaPluginFluidSynth.cpp +++ b/source/backend/plugin/CarlaPluginFluidSynth.cpp @@ -1563,7 +1563,7 @@ public: float vol = pData->postProc.volume; // Pan: Stereo only. - if ((pan != 0.0) and (q == 2)) + if ((pan != 0.0) && (q == 2)) { // left channel(s) reduce when pan to right if ((pan > 0) && (i == 0)) diff --git a/source/backend/plugin/CarlaPluginLADSPADSSI.cpp b/source/backend/plugin/CarlaPluginLADSPADSSI.cpp index f7bd8b1cb..9816d3da0 100644 --- a/source/backend/plugin/CarlaPluginLADSPADSSI.cpp +++ b/source/backend/plugin/CarlaPluginLADSPADSSI.cpp @@ -2134,7 +2134,7 @@ public: float vol = pData->postProc.volume; // Pan: Stereo, 3 ch (extra rear/bass), or Quadro. - if ((pan != 0.0) and ((q == 2) || (q == 3) || (q == 4))) + 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)))) @@ -2150,7 +2150,7 @@ public: } // Front-Rear: 3 ch (extra rear/bass), or Quadro. - if ((forth != 0.0) and ((q == 3) || (q == 4))) + if ((forth != 0.0) && ((q == 3) || (q == 4))) { // rear channel(s) reduce when moving forth to front if ((forth > 0) && ((i == 2) || (i == 3))) diff --git a/source/backend/plugin/CarlaPluginLV2.cpp b/source/backend/plugin/CarlaPluginLV2.cpp index 90829ae1e..eea0f794c 100644 --- a/source/backend/plugin/CarlaPluginLV2.cpp +++ b/source/backend/plugin/CarlaPluginLV2.cpp @@ -4725,7 +4725,7 @@ public: float vol = pData->postProc.volume; // Pan: Stereo, 3 ch (extra rear/bass), or Quadro. - if ((pan != 0.0) and ((q == 2) || (q == 3) || (q == 4))) + 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)))) @@ -4741,7 +4741,7 @@ public: } // Front-Rear: 3 ch (extra rear/bass), or Quadro. - if ((forth != 0.0) and ((q == 3) || (q == 4))) + if ((forth != 0.0) && ((q == 3) || (q == 4))) { // rear channel(s) reduce when moving forth to front if ((forth > 0) && ((i == 2) || (i == 3))) diff --git a/source/backend/plugin/CarlaPluginNative.cpp b/source/backend/plugin/CarlaPluginNative.cpp index 3651fa79b..80574501f 100644 --- a/source/backend/plugin/CarlaPluginNative.cpp +++ b/source/backend/plugin/CarlaPluginNative.cpp @@ -2432,7 +2432,7 @@ public: float vol = pData->postProc.volume; // Pan: Stereo, 3 ch (extra rear/bass), or Quadro. - if ((pan != 0.0) and ((q == 2) || (q == 3) || (q == 4))) + 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)))) @@ -2448,7 +2448,7 @@ public: } // Front-Rear: 3 ch (extra rear/bass), or Quadro. - if ((forth != 0.0) and ((q == 3) || (q == 4))) + if ((forth != 0.0) && ((q == 3) || (q == 4))) { // rear channel(s) reduce when moving forth to front if ((forth > 0) && ((i == 2) || (i == 3))) From 47efa2be5375cafd10d9dac6ff598c4fd83d3e77 Mon Sep 17 00:00:00 2001 From: falkTX Date: Sun, 27 Jul 2025 17:46:01 +0200 Subject: [PATCH 4/7] Sync qt_compat.py with main branch Signed-off-by: falkTX --- source/frontend/qt_compat.py | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/source/frontend/qt_compat.py b/source/frontend/qt_compat.py index 1c7262a1b..3cec424a1 100644 --- a/source/frontend/qt_compat.py +++ b/source/frontend/qt_compat.py @@ -32,8 +32,6 @@ elif qt_config == 6: QMessageBox, QSizePolicy, QStyle, - QToolButton, - QToolBar, ) Qt.AlignCenter = Qt.AlignmentFlag.AlignCenter @@ -62,16 +60,9 @@ elif qt_config == 6: Qt.CrossCursor = Qt.CursorShape.CrossCursor Qt.OpenHandCursor = Qt.CursorShape.OpenHandCursor Qt.PointingHandCursor = Qt.CursorShape.PointingHandCursor - Qt.RightArrow = Qt.ArrowType.RightArrow - Qt.DownArrow = Qt.ArrowType.DownArrow Qt.SizeAllCursor = Qt.CursorShape.SizeAllCursor Qt.SizeHorCursor = Qt.CursorShape.SizeHorCursor - Qt.TextSelectableByMouse = Qt.TextInteractionFlag.TextSelectableByMouse - - Qt.ToolButtonIconOnly = Qt.ToolButtonStyle.ToolButtonIconOnly - Qt.ToolButtonTextBesideIcon = Qt.ToolButtonStyle.ToolButtonTextBesideIcon - Qt.black = Qt.GlobalColor.black Qt.blue = Qt.GlobalColor.blue Qt.cyan = Qt.GlobalColor.cyan @@ -131,13 +122,6 @@ elif qt_config == 6: Qt.Key_X = Qt.Key.Key_X Qt.Key_Y = Qt.Key.Key_Y Qt.Key_Z = Qt.Key.Key_Z - Qt.Key_Space = Qt.Key.Key_Space - Qt.Key_Enter = Qt.Key.Key_Enter - Qt.Key_Return = Qt.Key.Key_Return - Qt.Key_PageUp = Qt.Key.Key_PageUp - Qt.Key_PageDown = Qt.Key.Key_PageDown - Qt.Key_Home = Qt.Key.Key_Home - Qt.Key_End = Qt.Key.Key_End Qt.AltModifier = Qt.KeyboardModifier.AltModifier Qt.ControlModifier = Qt.KeyboardModifier.ControlModifier From 6adfdf03741e97f9a314e84ff910b72d78b606d9 Mon Sep 17 00:00:00 2001 From: falkTX Date: Sun, 27 Jul 2025 21:57:23 +0200 Subject: [PATCH 5/7] Add missing resource Signed-off-by: falkTX --- resources/16x16/add-from-favorites.svgz | Bin 0 -> 6948 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 resources/16x16/add-from-favorites.svgz diff --git a/resources/16x16/add-from-favorites.svgz b/resources/16x16/add-from-favorites.svgz new file mode 100644 index 0000000000000000000000000000000000000000..602fee8f310b2c58b48959038cb747ac6430a2d8 GIT binary patch literal 6948 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 qa Date: Mon, 28 Jul 2025 20:54:31 +0300 Subject: [PATCH 6/7] Replace skin icon (#2012) * add-style-svg * add-style-svg --------- Co-authored-by: jpka- --- resources/16x16/skin.svgz | Bin 8109 -> 0 bytes resources/16x16/style-alt.svgz | Bin 0 -> 29253 bytes resources/16x16/style.svgz | Bin 0 -> 16660 bytes resources/resources.qrc | 2 +- resources/ui/carla_host.ui | 2 +- 5 files changed, 2 insertions(+), 2 deletions(-) delete mode 100644 resources/16x16/skin.svgz create mode 100644 resources/16x16/style-alt.svgz create mode 100644 resources/16x16/style.svgz diff --git a/resources/16x16/skin.svgz b/resources/16x16/skin.svgz deleted file mode 100644 index 3d4c901366874a8396ec7716098b4acec509d7e4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8109 zcmV;eA5!2SiwFP!000000DYTVj~qvmrQh`{V%aYZ4!1iE)0e0SIU*?S> zOBA;$mO!#w-TmwL`^>W_O4^!>pGtuA3wc(|N7;-$M+8}p1ps3_OIW5_09jdUXMS1czF5g z;q~$J+fP3n|Nj0@A78!v>EZaNA3lBh>G{`R|NQgMx3~9$x5p1}zW(Rqdj0mRufF;C zmp5M>$CuAc&phz<^^0eGw?F*+_VuS9UOXH9jE_G&ynXY-rx(xWsJG6~ilcvd`|$Js zeEjQ+XXkMqoO=BF>yNy}PyGM<_2aAeFMnk5k2fCh_x-f{(w={@j(Ysl!+2G`fA}t) z=jF&}uGe&4&-Kr736^^Q@%ht*FP{CQ7xh^me|q`q;p5j&$G^D#X;q)Ee>!mc`1t1! zuO7bV77w@g51+pNxBvXN&tG28+v`uSf4R-Cp8j)*=jQV~zvczMeAr*!{I_R*%cjc-zIw!3Z$JHQ$KU-4PyYG%!{g81`Qr~SUqAl*;#vOf34Zzc2Iuc@5X<`E z;nklWK79B1@&haX-6^jhKK|)xd9RcI{q~H%KR*8G-AgO2m9l>O{VSr7!?&7JZom7I zg|))P5^Iq3`z^5|nKRDwl2iEz!2i(tK4Qc#??^r_pN8kPU@~>}yeEau@ z|DS1I{rSU(hxebZ?_U1x0rR~192#$abK;v1kAME@$H&(sMx6WG8@+q^-NU;V&;I>K z-0|#}DB_0S#;0$7dim*xeVo+&^tX51==-z6O{c=a!SxZWN8JpIeZPahut z^zi%-XZ+khZZkelKmGOTz`M8aA9()r@BaMh)0fBo@8jF|&wqUT>EXlE(a*Pj_OuF? z_a8@@=OvxTACGd5+vuydOV78tPJTafnYUVMnwONut*lkoG5C%65| z+q|^OZTVS8o9lMYQU?n>Z=*9sCEw134Z;?clhaXKx{Z-$ zzp%2pYF^{Xp#SyR%!Yx^Z=a9+0z1Uhr@#I50N%a&^5-6e|6SCyGrvy#OJDwYl#VTX zJKJTBd}FO$&s_6u&6P9nLw=9mj_bkzal}~l?x^yNpp-D+;$qyWB6Xr zdgNYi*u0I)SQC5X(jk(0TP<_mm>G}P(T_CnP-!_`#whseOf%wP0TlBxy4Q}*dFOen zZQ%kIc58LCc8v43QXR$f3K6h+Ik-t$gGJxZ!)?6oE$0XwTtEp_zMH9s_`}!KGH-vbT|slnKgqlC)*%E9{0z1E)v6l$vkic|K<4B{NI7 zl(WS9Rx%Jrr;*krPjaJVmcj+3QD=F%;jFx7CLeEk@D}Bgap2UG^EOiHyWB zDXOpK&`uwjR%&C$=YGx$}zK zaSu#{Q-?7JV2-$$^Lw7;r!ZW6S3%({o_Q3lJ4(G^i8)T5bJUJo>q&f-TV|mkY2tAN z{2~>rP9@**14b_uPZO=;DDed%nz9%C0=%z7le7f#cjF1%03RpO0vy~{>3E5ZSOo4x z!0<_h+-pOFky&^OCunv5ArEb*9u`wo^fr9W$=cO z1Cd6aU{GT7JiENY$x_I5fH94A)pg$4Xb@LE_QVF*1QbsoFZacO`k(`8L?19hL?w?T z!lDvmybr5s5Ie_)vZ9ZE!9uLO!hQe*(xIYbP$j96nd*tMPJ#f35|$13L11tiNZ6#! zURflWEN`+*vCAcT!36m(ei$+MSd8Hz%!wF1zUM+ua{Fk zoX6jf9}nO&^*}jD4P+`{Uo)-2WuT)8k4xmh6w3_04zw8*10t+2hB&v9`3hul8tZ}S zh${pNKI9hU=&V8igh&sSG-MtXm9KJ=-9#OjXL3UnxI>^J5s6SjwGl>wE+kdVur?!e z*GfD9^LKFE3kawVdqrB}e4Ya|;BsCd6McY%Fzx*cbOt+~gaa1>4}Erk)I()?mXHeX z%NA!&8{uHDVtO?EJUoJ8AZo8fLYpcGR{%j(leQxEQm}Z~5OZIv-)0CZ?v8Zp5m*%y zwt!@orD|0O5o5{kG|aIYIq0F0tKy4P zDL{lTKrPNmAjM|MfsLRSZcSqNZqgH(9&e;%Lapbcp9Z2gH$9eVk-X>dta=j#rgT`+ zDI3a=BvC%pVLeGYvcM^%fe@@NxNj2V$s3`;$p{4@%LLJg>~_lLggM!GrdW9u+YF5+ zXqhC0`1EDSLBUH(LhR(YO!>7u2|(4)BzWqk27v-xrefpDrn=20Y|>P?e4=~8=o3blj&J&mCn5!T)8O`2j0G=4)EbZ290Q~3 z`ia|Gh1rM~qD(Gs)<=!J53J>hlsYgN7$D`sOGh1X29z;{d6g*u4Y*-+C=oOt8%(K5 zU<1%|Xb*%{XwY>Ue7+1n&6O5}Nsn|zSc*irE2-wbIE0m#dhzrElt(I;dh_xmbBhUb z`DVDQIKpWftZ%& zKnJiJo2*Q^NA(Dbn*UP`0H9LvvI1Twi+gA*RGR5nNNJ_lgmPn*Et6_-q=s4Yxf8#5 zPxDz{v6w_ja#>b#-LOO$m8@HE80fGKIjwYMwW7K~FNI4p<<7Lg#mwk=I4sfyqShqT zMKx?mo|Q28>fz6>&__JTtzfnH!Vo8~BAH?MsU{*dkdt_BK#N0sAVwes^+uO}!*4tv zdRx9ew9R)=3YEnrqJN3{OY$*Lc^wr*mK!n}GetBGN;rpGVgWb~%NocH?SX}B7JYjFD;E`LcZhP- zL-sMSwjqNMgk?d(&j%<5Dp#DA6}?6cQF;!Q-o~(ASfMpj@VNnfQmlYS)hL)LYR#mg ziK74{85Zux6|+Qn0gQG+H9{h}_KRWx@dqvi&a|~PKEE6an$yZf&__6_$H`T~H3ekP zwQu&`UGevz5aCH>(h-*Zt3~{?;$o!dn90} zSm^q#O(+&kliuq_q|-J9-vOU9gp-Ic(ms?Fj&>AtlS#-D= zYtViWBp1qaqD$~;@C7tHa@f7)n)Yi}>mzZAoR~RI%d3Z&Ak^Yd5K`aoc{qU z*<@K?c0efdHV67k)F+Lf8ev*2nh$kh>sv>mEW0Zn+%c+a9Gi*=YT9q>*QnsgPjnsCD zwnbzWxMafv~SyKLuH_NH#HoyKI8iSwWDvH=#_Du?}UdxD$a! zCfX>N<0;>b8VXX-(Y^&5XGG}@qSm;kMQIPdmx%Ivh@$lJC@IjLJWweGYkm8QTHlO;xeQHG}}LLF6pQ2rjlrBppCt0ii7m)ue5V zNrHxlrOO{-MwW0HmefE9i?_<=Ge;{G507ODS!%KF{4XpG$DlEmT_amfwd@G)BNU=Z zD%uQ})3w^_t_=}_7pOCpJj^z zX>DbGKayFtnz!xh>)~p5PHh49@p9m9`Q?CWin5HgD2i8bKWRXbEAz}XH$gyyAeB`n z#t11mIzsqjCM3%c)%_Y<@7ZJ(9c&z879nKvS|Jx z#OkV2>_u!#v6wYST{R``ZE{6+h-<8ZGvy1SYHC&3ev6Fzk$@(WSesiEhF_zVE^r8vSx|A&ML$2#IL}$TEBFh96EN9mN})J(e1Q z1yXsW7MEMfE_!Gx>>n-8}KntiOqbwz(D~;&mbY9u6sE0^w;|Dnk zWEk&q>QKK#tD5BFY%1cBC^ib&h!bt3L?dBXN_3NvkV0zl!gi)s1P+MWTR3aN7|kJ> zy_P~^UGCAeN=jhs1!<$#!a{Wak_Ti%#o1d6k+LeBxXP zPB z$)Y60HOj5bEmTl?uDnFI1dM>-3YwJc0*Acq7l)k9Eu;Jt&qhltG+l|Lxu)oAr`@v; z9?^vu?@%*kaK|a1y{=FZm~hYL1~!2g)e-&PkGET8vwiz&&p1N$g;Z%R1_KCZ;zUnP zo3HWG-2!qb80>3iZ%6cI$J-N;YLy?OE>5vNs?9d;6b-gH z0}MeW5D0t3e3%Ssn=rupY&2;}3a3DP!xoi?Dv2(?^-_wq91K?u^{SL7e!@W2^_3=N z+7ct6jX(QRs!N4`kGee1Vkv011xUrZt*shrCtJJ8oH#i2Wn}N6V@eJ$g8&NgYg z1CAMGZ3Dk1alC8z)8=))H{FQ)5mha56kU?eB5gf}649e2MT=JFD8+0oG<{)&@zV8P zst5Wt^{{&&WJOS!U4LI#A*qs!G=Ov#GYrwcP`HJSTMqU#cTwH>0KRjr@M9-ExIo2LU!Ck*j|X! zpP4r9uMv)KEye}aPU=4Lt2hn;bD|$wka~xSOO&tubR^<95#cg)94f;eMpdDefKgsA zSr*x_Ly!g8DQWh-iiN+gOnJT=(zXnPQ_nMo6Z@B)IQrI_BR0Gk{*g6mk^q>~sg@Ir zBlP_l%|Y8Z?T%vrTR$CTMP;o@ugY}zVR)d!!9mkDz*oGqCDrALjAMU7b0#bai{nNHI!+ z>ET@WMr2z^(O@Xi7~uU%#8~TReR9CqLW#v(qGP}I#3MPk_XzIW(uuV=^s(BD#vsYi z3?wM@QpAwc-9wQKNaZs`7180ODUf3XrX#m>sA6%3ri$XEtmQV=IDs z5wSB0NFS*ZUZl?)yTgda68{}g#%yjk!gB0x!2&H5VstxIKTf`^KFtWEL)*k*9xdr; z25#jOH5TQH@-GH2+%N=iBMKa%MmMQ+H@dL!)}m#YemT;k|tQR}iFOIAj3Cz19Qr1VC`iurk8xEREym4N!XI#;`hH8V#YcYK)f2>Si^A;Xs~feU5;CxM_~S<~GolesF6S7yL6H!RG9HNN5gLf+5%LRMG>?K**0}E*5Y%@g%v}M^DlT|z zwH@EXQAjjNxl8LYIIXc42Xb}W$f3Sjtw-pld(+#85K=;u#e>LpZ>?v^G`4$0$&hW4 zWpvxM>ks0dn9IOL30_8>4x*5kn9XOdGd!$7ED<tX{ zz)`TY70Ct0VRbz;uw{QxEv&Ew)*ALl-GdN)LMvzof4!=~LZ!Po$SU5NJ4&`|?D9fA zU0n5;p(gMP??$qi8dUUq$G~(OpAEyTxNE1KaaHY}<2%`BJ?`)6ImQ#k2_bppwh{DJ zG{c*Uq6AFlvS;umMn%Upc6qhfkz|P=Kx2P@v7NGPRn_{uQ+q;e}FSsl}rty zhqhI$bz(xDP?eY79gyI?oGzv_Cfsf_U;qGMmb2$ZXbDV97C~J%eh| znsYaenyuwxVggA$>M!a%OB{C&i-=dV0dV8aBWAuK7|xMej3cY%@9sE^9=p)ob1R(m z{Q{8o}OZOfbw+%le!8RUbcLimAQq(bk8;Jmr*PhARS#^-9T`) zxrSoGY<7I+i~&j;X&Z@}^B(4@9sm0PIH5WFCdw!G>^gVgP>vQ!4s3}*REp}c=XhWJ zk=^`Y(c5A^_oNL&2jRaK+d3i-YHW2Hd!L6hZ%NNVw!vX#Yv%+_P>i0_t%R`^WCR%9 z)Ppfg=+1MF6xl~jgAC2~acfk&6>-~QOtVU82E}a?RbvaLRUP49V|)!88YNgWvJMS+4~*!}kP^!56hufCxIrzaFwyDm_~Gt7L{{J9 z)z#f6=*FWEQ9RR4V+4owL%DZXo(8y7)Aw!55i};r{%6bYGj9l6C3e-Ry;ihR43$y0 z0)cs*z}n-;p_37yBRW)Wp^gzi=pqdTbLidGf4 z-}a=S^bl`#kKR^HbDB8T0(!a~nrpR#F;m+kw@`BJyf<_O)D|5r%R)JOmAXTm#0rxu zJc4I97uZmvxlz~7wh1(CDl5Y7m!_D5MPc1kq`?x!jlR)bv~>}k%DG}M{T|tdv`lWV zS9!D)8XNqaZiqW~XV~p!wqo5)z&V@cGTLYa);fxXXLk56-n_?#2j{--6;_*CGsbLw z*k6kticN>4$GTl0jHo1&6q2#;j!5bPGjx&5QE9imLoHY;oInI=ch!kI>+I>|xp(t| zj)K0l+V)_%ipm$e7RzcbhveH`JiGEUfs>?v8H)S0Tw zQTOz@?7td|QPr5Srw<(HX=gxL(Pf_!wemUdYA|%K@)_(+%OQjRO7dsci$Xm~6AjMT zhGB1II8~M)pgsL+vrWc`9UXmM91iCgW_CNnIQ5G|-mutq4wqO*M@`xsCaQyW0cKCz z;bh3_WHSw67-Nkeb2DL@EmuM-*FL9*A zSW($>miRM@Gx{JRu{%KR()Di5?PS!-aM^gvahFc**0m`J*J5hN(rv(0w;F^=OAGYE zkWHu^?lHC~i}77Z9LFP~8mGF`VZ^SBy&%Z!5Xf(Ht>JsjrcLuO}F&Yc4u^}Su6d+<&K&`#l zUC*OQEdz(#iB^d{gQLXwUZpx`Odi(gdwSp8&?<(c^V|&*lF2RER0^jc9@QYz~{Yog+eAdkI1m?WlyLkNy- zoFhC~$CN|% H+&usQ5VFYM diff --git a/resources/16x16/style-alt.svgz b/resources/16x16/style-alt.svgz new file mode 100644 index 0000000000000000000000000000000000000000..6576e0d33ea2e2992e619597acb802a4fbb48949 GIT binary patch literal 29253 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 literal 0 HcmV?d00001 diff --git a/resources/16x16/style.svgz b/resources/16x16/style.svgz new file mode 100644 index 0000000000000000000000000000000000000000..7105399b51cbb215e3bc32e4410208da68b0aae2 GIT binary patch literal 16660 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 a&E@z@_V)2Mf4l1c*)!=+SUs_pnE?QfUkEG! literal 0 HcmV?d00001 diff --git a/resources/resources.qrc b/resources/resources.qrc index 7183bdb78..2cc9a3a98 100644 --- a/resources/resources.qrc +++ b/resources/resources.qrc @@ -36,7 +36,7 @@ 16x16/media-seek-forward.svgz 16x16/network-connect.svgz 16x16/restore.svgz - 16x16/skin.svgz + 16x16/style.svgz 16x16/system-shutdown.svgz 16x16/system-turnon.svgz 16x16/view-refresh.svgz diff --git a/resources/ui/carla_host.ui b/resources/ui/carla_host.ui index a68581649..9fefae396 100644 --- a/resources/ui/carla_host.ui +++ b/resources/ui/carla_host.ui @@ -1634,7 +1634,7 @@ - :/16x16/skin.svgz:/16x16/skin.svgz + :/16x16/style.svgz:/16x16/style.svgz Change &Skin... From 5e505428f3e9c35fc219a3e3d307be5dd5a41c67 Mon Sep 17 00:00:00 2001 From: falkTX Date: Sun, 3 Aug 2025 18:30:35 +0200 Subject: [PATCH 7/7] Remove "forth" related API Signed-off-by: falkTX --- source/backend/CarlaBackend.h | 14 +----- source/backend/CarlaHost.h | 7 --- source/backend/CarlaPlugin.hpp | 10 ----- source/backend/CarlaStandalone.cpp | 8 ---- source/backend/engine/CarlaEngine.cpp | 2 - source/backend/engine/CarlaEngineNative.cpp | 11 ----- source/backend/engine/CarlaEngineOsc.hpp | 1 - .../backend/engine/CarlaEngineOscHandlers.cpp | 13 ------ source/backend/engine/CarlaEngineOscSend.cpp | 11 +++-- source/backend/plugin/CarlaPlugin.cpp | 44 ------------------- source/backend/plugin/CarlaPluginInternal.cpp | 1 - source/backend/plugin/CarlaPluginInternal.hpp | 1 - .../backend/plugin/CarlaPluginLADSPADSSI.cpp | 24 +--------- source/backend/plugin/CarlaPluginLV2.cpp | 24 +--------- source/backend/plugin/CarlaPluginNative.cpp | 24 +--------- source/frontend/carla_backend.py | 31 +------------ source/frontend/carla_backend_qtweb.py | 6 --- source/frontend/carla_host.py | 1 - source/frontend/carla_host_control.py | 6 +-- source/frontend/carla_skin.py | 12 ----- source/frontend/carla_widgets.py | 25 +---------- source/frontend/dialogs/aboutdialog.cpp | 1 - source/frontend/widgets/commondial.py | 8 ++-- source/frontend/widgets/scalabledial.py | 3 +- source/rest/carla-host.cpp | 14 ------ source/rest/rest-server.cpp | 1 - source/utils/CarlaBackendUtils.hpp | 2 - source/utils/CarlaStateUtils.cpp | 8 ---- source/utils/CarlaStateUtils.hpp | 1 - 29 files changed, 21 insertions(+), 293 deletions(-) diff --git a/source/backend/CarlaBackend.h b/source/backend/CarlaBackend.h index cafa55864..33d9a51c4 100644 --- a/source/backend/CarlaBackend.h +++ b/source/backend/CarlaBackend.h @@ -210,11 +210,6 @@ static constexpr const uint PLUGIN_HAS_CUSTOM_UI_USING_FILE_OPEN = 0x2000; */ static constexpr const uint PLUGIN_NEEDS_MAIN_THREAD_IDLE = 0x4000; -/*! - * Plugin can use internal Front-Rear balance for Quadro (or > 2 channels in General). - */ -static constexpr const uint PLUGIN_CAN_FORTH = 0x8000; - /** @} */ /* ------------------------------------------------------------------------------------------------------------ @@ -838,19 +833,12 @@ typedef enum { * Range -1...15 (-1 = off). */ PARAMETER_CTRL_CHANNEL = -8, - - /*! - * Experimental Front-Rear parameter for Quadro (or > 2 channels in General). - * Range -1.0...1.0; default is 0.0. - */ - PARAMETER_FORTH = -9, - #endif /*! * Max value, defined only for convenience. */ - PARAMETER_MAX = -10 + PARAMETER_MAX = -9 } InternalParameterIndex; diff --git a/source/backend/CarlaHost.h b/source/backend/CarlaHost.h index bbd412df1..240ba0c29 100644 --- a/source/backend/CarlaHost.h +++ b/source/backend/CarlaHost.h @@ -966,13 +966,6 @@ CARLA_API_EXPORT void carla_set_balance_right(CarlaHostHandle handle, uint plugi */ CARLA_API_EXPORT void carla_set_panning(CarlaHostHandle handle, uint pluginId, float value); -/*! - * Change a plugin's internal experimental front-rear (forth) value. - * @param pluginId Plugin - * @param value New value - */ -CARLA_API_EXPORT void carla_set_forth(CarlaHostHandle handle, uint pluginId, float value); - /*! * Change a plugin's internal control channel. * @param pluginId Plugin diff --git a/source/backend/CarlaPlugin.hpp b/source/backend/CarlaPlugin.hpp index 48e1ae47f..5ae7f50f7 100644 --- a/source/backend/CarlaPlugin.hpp +++ b/source/backend/CarlaPlugin.hpp @@ -542,15 +542,6 @@ public: */ void setPanning(float value, bool sendOsc, bool sendCallback) noexcept; - /*! - * Set the plugin's output forth (front-rear) value to @a value. - * @a value must be between -1.0 and 1.0. - * - * @param sendOsc Send message change over OSC - * @param sendCallback Send message change to registered callback - */ - void setForth(float value, bool sendOsc, bool sendCallback) noexcept; - /*! * Overloaded functions, to be called from within RT context only. */ @@ -559,7 +550,6 @@ public: void setBalanceLeftRT(float value, bool sendCallbackLater) noexcept; void setBalanceRightRT(float value, bool sendCallbackLater) noexcept; void setPanningRT(float value, bool sendCallbackLater) noexcept; - void setForthRT(float value, bool sendCallbackLater) noexcept; #endif // ! BUILD_BRIDGE_ALTERNATIVE_ARCH /*! diff --git a/source/backend/CarlaStandalone.cpp b/source/backend/CarlaStandalone.cpp index 9dee9b705..6a96894e1 100644 --- a/source/backend/CarlaStandalone.cpp +++ b/source/backend/CarlaStandalone.cpp @@ -2091,14 +2091,6 @@ void carla_set_panning(CarlaHostHandle handle, uint pluginId, float value) plugin->setPanning(value, true, false); } -void carla_set_forth(CarlaHostHandle handle, uint pluginId, float value) -{ - CARLA_SAFE_ASSERT_RETURN(handle->engine != nullptr,); - - if (const CarlaPluginPtr plugin = handle->engine->getPlugin(pluginId)) - plugin->setForth(value, true, false); -} - void carla_set_ctrl_channel(CarlaHostHandle handle, uint pluginId, int8_t channel) { CARLA_SAFE_ASSERT_RETURN(handle->engine != nullptr,); diff --git a/source/backend/engine/CarlaEngine.cpp b/source/backend/engine/CarlaEngine.cpp index ad4d61c92..1731cd45e 100644 --- a/source/backend/engine/CarlaEngine.cpp +++ b/source/backend/engine/CarlaEngine.cpp @@ -2921,7 +2921,6 @@ bool CarlaEngine::loadProjectInternal(water::XmlDocument& xmlDoc, const bool alw plugin->setBalanceLeft(stateSave.balanceLeft, true, true); plugin->setBalanceRight(stateSave.balanceRight, true, true); plugin->setPanning(stateSave.panning, true, true); - plugin->setForth(stateSave.forth, true, true); plugin->setCtrlChannel(stateSave.ctrlChannel, true, true); plugin->setActive(stateSave.active, true, true); plugin->setEnabled(true); @@ -2977,7 +2976,6 @@ bool CarlaEngine::loadProjectInternal(water::XmlDocument& xmlDoc, const bool alw plugin->setBalanceLeft(stateSave.balanceLeft, true, true); plugin->setBalanceRight(stateSave.balanceRight, true, true); plugin->setPanning(stateSave.panning, true, true); - plugin->setForth(stateSave.forth, true, true); plugin->setCtrlChannel(stateSave.ctrlChannel, true, true); plugin->setActive(stateSave.active, true, true); plugin->setEnabled(true); diff --git a/source/backend/engine/CarlaEngineNative.cpp b/source/backend/engine/CarlaEngineNative.cpp index ec34896db..0534b82a6 100644 --- a/source/backend/engine/CarlaEngineNative.cpp +++ b/source/backend/engine/CarlaEngineNative.cpp @@ -2115,17 +2115,6 @@ bool CarlaEngineNativeUI::msgReceived(const char* const msg) noexcept if (const CarlaPluginPtr plugin = fEngine->getPlugin(pluginId)) plugin->setPanning(value, true, false); } - else if (std::strcmp(msg, "set_forth") == 0) - { - uint32_t pluginId; - float value; - - CARLA_SAFE_ASSERT_RETURN(readNextLineAsUInt(pluginId), true); - CARLA_SAFE_ASSERT_RETURN(readNextLineAsFloat(value), true); - - if (const CarlaPluginPtr plugin = fEngine->getPlugin(pluginId)) - plugin->setForth(value, true, false); - } else if (std::strcmp(msg, "set_ctrl_channel") == 0) { uint32_t pluginId; diff --git a/source/backend/engine/CarlaEngineOsc.hpp b/source/backend/engine/CarlaEngineOsc.hpp index d20a3b18b..4677f1ec3 100644 --- a/source/backend/engine/CarlaEngineOsc.hpp +++ b/source/backend/engine/CarlaEngineOsc.hpp @@ -137,7 +137,6 @@ private: int handleMsgSetBalanceLeft(CARLA_ENGINE_OSC_HANDLE_ARGS); int handleMsgSetBalanceRight(CARLA_ENGINE_OSC_HANDLE_ARGS); int handleMsgSetPanning(CARLA_ENGINE_OSC_HANDLE_ARGS); - int handleMsgSetForth(CARLA_ENGINE_OSC_HANDLE_ARGS); int handleMsgSetParameterValue(CARLA_ENGINE_OSC_HANDLE_ARGS); int handleMsgSetParameterMappedControlIndex(CARLA_ENGINE_OSC_HANDLE_ARGS); int handleMsgSetParameterMappedRange(CARLA_ENGINE_OSC_HANDLE_ARGS); diff --git a/source/backend/engine/CarlaEngineOscHandlers.cpp b/source/backend/engine/CarlaEngineOscHandlers.cpp index 3a98485fd..1258c302c 100644 --- a/source/backend/engine/CarlaEngineOscHandlers.cpp +++ b/source/backend/engine/CarlaEngineOscHandlers.cpp @@ -179,8 +179,6 @@ int CarlaEngineOsc::handleMessage(const bool isTCP, const char* const path, return handleMsgSetBalanceRight(plugin, argc, argv, types); if (std::strcmp(method, "set_panning") == 0) return handleMsgSetPanning(plugin, argc, argv, types); - if (std::strcmp(method, "set_forth") == 0) - return handleMsgSetForth(plugin, argc, argv, types); if (std::strcmp(method, "set_ctrl_channel") == 0) return 0; //handleMsgSetControlChannel(plugin, argc, argv, types); // TODO if (std::strcmp(method, "set_parameter_value") == 0) @@ -680,17 +678,6 @@ int CarlaEngineOsc::handleMsgSetPanning(CARLA_ENGINE_OSC_HANDLE_ARGS) return 0; } -int CarlaEngineOsc::handleMsgSetForth(CARLA_ENGINE_OSC_HANDLE_ARGS) -{ - carla_debug("CarlaEngineOsc::handleMsgSetForth()"); - CARLA_ENGINE_OSC_CHECK_OSC_TYPES(1, "f"); - - const float value = argv[0]->f; - - plugin->setForth(value, false, true); - return 0; -} - int CarlaEngineOsc::handleMsgSetParameterValue(CARLA_ENGINE_OSC_HANDLE_ARGS) { carla_debug("CarlaEngineOsc::handleMsgSetParameterValue()"); diff --git a/source/backend/engine/CarlaEngineOscSend.cpp b/source/backend/engine/CarlaEngineOscSend.cpp index 9fd1dd7c3..00207e99f 100644 --- a/source/backend/engine/CarlaEngineOscSend.cpp +++ b/source/backend/engine/CarlaEngineOscSend.cpp @@ -287,18 +287,18 @@ void CarlaEngineOsc::sendPluginInternalParameterValues(const CarlaPluginPtr& plu carla_debug("CarlaEngineOsc::sendPluginInternalParameterValues(%p)", plugin.get()); #ifdef CARLA_PROPER_CPP11_SUPPORT - static_assert(PARAMETER_ACTIVE == -2 && PARAMETER_MAX == -10, "Incorrect data"); + static_assert(PARAMETER_ACTIVE == -2 && PARAMETER_MAX == -9, "Incorrect data"); #endif - double iparams[8]; + double iparams[7]; - for (int32_t i = 0; i < 8; ++i) + for (int32_t i = 0; i < 7; ++i) iparams[i] = plugin->getInternalParameterValue(PARAMETER_ACTIVE - i); char targetPath[std::strlen(fControlDataTCP.path)+9]; std::strcpy(targetPath, fControlDataTCP.path); std::strcat(targetPath, "/iparams"); - try_lo_send(fControlDataTCP.target, targetPath, "iffffffff", + try_lo_send(fControlDataTCP.target, targetPath, "ifffffff", static_cast(plugin->getId()), iparams[0], // PARAMETER_ACTIVE iparams[1], // PARAMETER_DRYWET @@ -306,8 +306,7 @@ void CarlaEngineOsc::sendPluginInternalParameterValues(const CarlaPluginPtr& plu iparams[3], // PARAMETER_BALANCE_LEFT iparams[4], // PARAMETER_BALANCE_RIGHT iparams[5], // PARAMETER_PANNING - iparams[6], // PARAMETER_CTRL_CHANNEL - iparams[7] // PARAMETER_FORTH + iparams[6] // PARAMETER_CTRL_CHANNEL ); } diff --git a/source/backend/plugin/CarlaPlugin.cpp b/source/backend/plugin/CarlaPlugin.cpp index 592fb25d5..afc7d108b 100644 --- a/source/backend/plugin/CarlaPlugin.cpp +++ b/source/backend/plugin/CarlaPlugin.cpp @@ -392,8 +392,6 @@ float CarlaPlugin::getInternalParameterValue(const int32_t parameterId) const no return pData->postProc.balanceRight; case PARAMETER_PANNING: return pData->postProc.panning; - case PARAMETER_FORTH: - return pData->postProc.forth; }; #endif CARLA_SAFE_ASSERT_RETURN(parameterId >= 0, 0.0f); @@ -542,7 +540,6 @@ const CarlaStateSave& CarlaPlugin::getStateSave(const bool callPrepareForSave) pData->stateSave.balanceLeft = pData->postProc.balanceLeft; pData->stateSave.balanceRight = pData->postProc.balanceRight; pData->stateSave.panning = pData->postProc.panning; - pData->stateSave.forth = pData->postProc.forth; pData->stateSave.ctrlChannel = pData->ctrlChannel; #endif @@ -956,7 +953,6 @@ void CarlaPlugin::loadStateSave(const CarlaStateSave& stateSave) setBalanceLeft(stateSave.balanceLeft, true, true); setBalanceRight(stateSave.balanceRight, true, true); setPanning(stateSave.panning, true, true); - setForth(stateSave.forth, true, true); setCtrlChannel(stateSave.ctrlChannel, true, true); setActive(stateSave.active, true, true); @@ -1554,31 +1550,6 @@ void CarlaPlugin::setPanning(const float value, const bool sendOsc, const bool s nullptr); } -void CarlaPlugin::setForth(const float value, const bool sendOsc, const bool sendCallback) noexcept -{ - if (pData->engineBridged) { - CARLA_SAFE_ASSERT_RETURN(!sendOsc && !sendCallback,); - } else { - CARLA_SAFE_ASSERT_RETURN(sendOsc || sendCallback,); // never call this from RT - } - CARLA_SAFE_ASSERT(value >= -1.0f && value <= 1.0f); - - const float fixedValue(carla_fixedValue(-1.0f, 1.0f, value)); - - if (carla_isEqual(pData->postProc.forth, fixedValue)) - return; - - pData->postProc.forth = fixedValue; - - pData->engine->callback(sendCallback, sendOsc, - ENGINE_CALLBACK_PARAMETER_VALUE_CHANGED, - pData->id, - PARAMETER_FORTH, - 0, 0, - fixedValue, - nullptr); -} - void CarlaPlugin::setDryWetRT(const float value, const bool sendCallbackLater) noexcept { CARLA_SAFE_ASSERT(value >= 0.0f && value <= 1.0f); @@ -1643,19 +1614,6 @@ void CarlaPlugin::setPanningRT(const float value, const bool sendCallbackLater) pData->postProc.panning = fixedValue; pData->postponeParameterChangeRtEvent(sendCallbackLater, PARAMETER_PANNING, fixedValue); } - -void CarlaPlugin::setForthRT(const float value, const bool sendCallbackLater) noexcept -{ - CARLA_SAFE_ASSERT(value >= -1.0f && value <= 1.0f); - - const float fixedValue(carla_fixedValue(-1.0f, 1.0f, value)); - - if (carla_isEqual(pData->postProc.forth, fixedValue)) - return; - - pData->postProc.forth = fixedValue; - pData->postponeParameterChangeRtEvent(sendCallbackLater, PARAMETER_FORTH, fixedValue); -} #endif // ! BUILD_BRIDGE_ALTERNATIVE_ARCH void CarlaPlugin::setCtrlChannel(const int8_t channel, const bool sendOsc, const bool sendCallback) noexcept @@ -1737,8 +1695,6 @@ void CarlaPlugin::setParameterValueByRealIndex(const int32_t rindex, const float return setBalanceRight(value, sendOsc, sendCallback); case PARAMETER_PANNING: return setPanning(value, sendOsc, sendCallback); - case PARAMETER_FORTH: - return setForth(value, sendOsc, sendCallback); } #endif CARLA_SAFE_ASSERT_RETURN(rindex >= 0,); diff --git a/source/backend/plugin/CarlaPluginInternal.cpp b/source/backend/plugin/CarlaPluginInternal.cpp index 53307b9db..3f7bbd2e9 100644 --- a/source/backend/plugin/CarlaPluginInternal.cpp +++ b/source/backend/plugin/CarlaPluginInternal.cpp @@ -710,7 +710,6 @@ CarlaPlugin::ProtectedData::PostProc::PostProc() noexcept balanceLeft(-1.0f), balanceRight(1.0f), panning(0.0f), - forth(0.0f), extraBuffer(nullptr) {} #endif diff --git a/source/backend/plugin/CarlaPluginInternal.hpp b/source/backend/plugin/CarlaPluginInternal.hpp index eb576cafc..8a420caf3 100644 --- a/source/backend/plugin/CarlaPluginInternal.hpp +++ b/source/backend/plugin/CarlaPluginInternal.hpp @@ -379,7 +379,6 @@ struct CarlaPlugin::ProtectedData { float balanceLeft; float balanceRight; float panning; - float forth; float* extraBuffer; PostProc() noexcept; diff --git a/source/backend/plugin/CarlaPluginLADSPADSSI.cpp b/source/backend/plugin/CarlaPluginLADSPADSSI.cpp index 9816d3da0..8f2ff46bb 100644 --- a/source/backend/plugin/CarlaPluginLADSPADSSI.cpp +++ b/source/backend/plugin/CarlaPluginLADSPADSSI.cpp @@ -1273,9 +1273,6 @@ public: if (aOuts >= 2) pData->hints |= PLUGIN_CAN_PANNING; - - if (aOuts >= 3) - pData->hints |= PLUGIN_CAN_FORTH; #endif // extra plugin hints @@ -2124,13 +2121,12 @@ public: } } - // Panning and Front-Rear ("Forth"). + // Panning // Only decrease of levels, but never increase, unlike 'L, R'. - // Note: no any pan/forth processing for Mono. + // Note: no pan processing for Mono. uint32_t q = pData->audioOut.count; float pan = pData->postProc.panning; - float forth = pData->postProc.forth; float vol = pData->postProc.volume; // Pan: Stereo, 3 ch (extra rear/bass), or Quadro. @@ -2149,22 +2145,6 @@ public: } } - // Front-Rear: 3 ch (extra rear/bass), or Quadro. - if ((forth != 0.0) && ((q == 3) || (q == 4))) - { - // rear channel(s) reduce when moving forth to front - if ((forth > 0) && ((i == 2) || (i == 3))) - { - vol = vol * (1.0 - forth); - } - - // front channels reduce when moving back to rear - else if ((forth < 0) && ((i == 0) || (i == 1))) - { - vol = vol * (1.0 + forth); - } - } - // Volume (and buffer copy) { for (uint32_t k=0; k < frames; ++k) diff --git a/source/backend/plugin/CarlaPluginLV2.cpp b/source/backend/plugin/CarlaPluginLV2.cpp index eea0f794c..c26dc2d8a 100644 --- a/source/backend/plugin/CarlaPluginLV2.cpp +++ b/source/backend/plugin/CarlaPluginLV2.cpp @@ -3341,9 +3341,6 @@ public: if (aOuts >= 2) pData->hints |= PLUGIN_CAN_PANNING; - if (aOuts >= 3) - pData->hints |= PLUGIN_CAN_FORTH; - // extra plugin hints pData->extraHints = 0x0; @@ -4715,13 +4712,12 @@ public: } } - // Panning and Front-Rear ("Forth"). + // Panning // Only decrease of levels, but never increase, unlike 'L, R'. - // Note: no any pan/forth processing for Mono. + // Note: no pan processing for Mono. uint32_t q = pData->audioOut.count; float pan = pData->postProc.panning; - float forth = pData->postProc.forth; float vol = pData->postProc.volume; // Pan: Stereo, 3 ch (extra rear/bass), or Quadro. @@ -4740,22 +4736,6 @@ public: } } - // Front-Rear: 3 ch (extra rear/bass), or Quadro. - if ((forth != 0.0) && ((q == 3) || (q == 4))) - { - // rear channel(s) reduce when moving forth to front - if ((forth > 0) && ((i == 2) || (i == 3))) - { - vol = vol * (1.0 - forth); - } - - // front channels reduce when moving back to rear - else if ((forth < 0) && ((i == 0) || (i == 1))) - { - vol = vol * (1.0 + forth); - } - } - // Volume (and buffer copy) { for (uint32_t k=0; k < frames; ++k) diff --git a/source/backend/plugin/CarlaPluginNative.cpp b/source/backend/plugin/CarlaPluginNative.cpp index 80574501f..dc4550e27 100644 --- a/source/backend/plugin/CarlaPluginNative.cpp +++ b/source/backend/plugin/CarlaPluginNative.cpp @@ -1408,9 +1408,6 @@ public: if (aOuts >= 2) pData->hints |= PLUGIN_CAN_PANNING; - if (aOuts >= 3) - pData->hints |= PLUGIN_CAN_FORTH; - // native plugin hints if (fDescriptor->hints & NATIVE_PLUGIN_IS_RTSAFE) pData->hints |= PLUGIN_IS_RTSAFE; @@ -2422,13 +2419,12 @@ public: } } - // Panning and Front-Rear ("Forth"). + // Panning // Only decrease of levels, but never increase, unlike 'L, R'. - // Note: no any pan/forth processing for Mono. + // Note: no pan processing for Mono. uint32_t q = pData->audioOut.count; float pan = pData->postProc.panning; - float forth = pData->postProc.forth; float vol = pData->postProc.volume; // Pan: Stereo, 3 ch (extra rear/bass), or Quadro. @@ -2447,22 +2443,6 @@ public: } } - // Front-Rear: 3 ch (extra rear/bass), or Quadro. - if ((forth != 0.0) && ((q == 3) || (q == 4))) - { - // rear channel(s) reduce when moving forth to front - if ((forth > 0) && ((i == 2) || (i == 3))) - { - vol = vol * (1.0 - forth); - } - - // front channels reduce when moving back to rear - else if ((forth < 0) && ((i == 0) || (i == 1))) - { - vol = vol * (1.0 + forth); - } - } - // Volume (and buffer copy) { for (uint32_t k=0; k < frames; ++k) diff --git a/source/frontend/carla_backend.py b/source/frontend/carla_backend.py index 4d1a440f0..e6c1072d5 100644 --- a/source/frontend/carla_backend.py +++ b/source/frontend/carla_backend.py @@ -207,9 +207,6 @@ PLUGIN_USES_MULTI_PROGS = 0x400 # Plugin can make use of inline display API. PLUGIN_HAS_INLINE_DISPLAY = 0x800 -# Plugin can use internal control of Front-Rear balance for Quadro (or > 2 channels in General). -PLUGIN_CAN_FORTH = 0x8000 - # --------------------------------------------------------------------------------------------------------------------- # Plugin Options # Various plugin options. @@ -556,12 +553,8 @@ PARAMETER_PANNING = -7 # Range -1...15 (-1 = off). PARAMETER_CTRL_CHANNEL = -8 -# Experimental Front-Rear (Forth) parameter. -# Range -1.0...1.0; default is 0.0. -PARAMETER_FORTH = -9 - # Max value, defined only for convenience. -PARAMETER_MAX = -10 +PARAMETER_MAX = -9 # --------------------------------------------------------------------------------------------------------------------- # Special Mapped Control Index @@ -2119,13 +2112,6 @@ class CarlaHostMeta(): def set_panning(self, pluginId, value): raise NotImplementedError - # Change a plugin's internal front-rear (forth) value. - # @param pluginId Plugin - # @param value New value - @abstractmethod - def set_forth(self, pluginId, value): - raise NotImplementedError - # Change a plugin's internal control channel. # @param pluginId Plugin # @param channel New channel @@ -2544,9 +2530,6 @@ class CarlaHostNull(CarlaHostMeta): def set_panning(self, pluginId, value): return - def set_forth(self, pluginId, value): - return - def set_ctrl_channel(self, pluginId, channel): return @@ -2869,9 +2852,6 @@ class CarlaHostDLL(CarlaHostMeta): self.lib.carla_set_panning.argtypes = (c_void_p, c_uint, c_float) self.lib.carla_set_panning.restype = None - self.lib.carla_set_forth.argtypes = (c_void_p, c_uint, c_float) - self.lib.carla_set_forth.restype = None - self.lib.carla_set_ctrl_channel.argtypes = (c_void_p, c_uint, c_int8) self.lib.carla_set_ctrl_channel.restype = None @@ -3213,9 +3193,6 @@ class CarlaHostDLL(CarlaHostMeta): def set_panning(self, pluginId, value): self.lib.carla_set_panning(self.handle, pluginId, value) - def set_forth(self, pluginId, value): - self.lib.carla_set_forth(self.handle, pluginId, value) - def set_ctrl_channel(self, pluginId, channel): self.lib.carla_set_ctrl_channel(self.handle, pluginId, channel) @@ -3296,7 +3273,7 @@ class PluginStoreInfo(): def clear(self): self.pluginInfo = PyCarlaPluginInfo.copy() self.pluginRealName = "" - self.internalValues = [0.0, 1.0, 1.0, -1.0, 1.0, 0.0, -1.0, 0.0] + self.internalValues = [0.0, 1.0, 1.0, -1.0, 1.0, 0.0, -1.0] self.audioCountInfo = PyCarlaPortCountInfo.copy() self.midiCountInfo = PyCarlaPortCountInfo.copy() self.parameterCount = 0 @@ -3623,10 +3600,6 @@ class CarlaHostPlugin(CarlaHostMeta): self.sendMsg(["set_panning", pluginId, value]) self.fPluginsInfo[pluginId].internalValues[5] = value - def set_forth(self, pluginId, value): - self.sendMsg(["set_forth", pluginId, value]) - self.fPluginsInfo[pluginId].internalValues[7] = value - def set_ctrl_channel(self, pluginId, channel): self.sendMsg(["set_ctrl_channel", pluginId, channel]) self.fPluginsInfo[pluginId].internalValues[6] = float(channel) diff --git a/source/frontend/carla_backend_qtweb.py b/source/frontend/carla_backend_qtweb.py index 90a6888ee..c10f0e214 100644 --- a/source/frontend/carla_backend_qtweb.py +++ b/source/frontend/carla_backend_qtweb.py @@ -468,12 +468,6 @@ class CarlaHostQtWeb(CarlaHostQtNull): 'value': value, }) - def set_forth(self, pluginId, value): - requests.get("{}/set_forth".format(self.baseurl), params={ - 'pluginId': pluginId, - 'value': value, - }) - def set_ctrl_channel(self, pluginId, channel): requests.get("{}/set_ctrl_channel".format(self.baseurl), params={ 'pluginId': pluginId, diff --git a/source/frontend/carla_host.py b/source/frontend/carla_host.py index 78bc9d3f7..2b15f29f3 100644 --- a/source/frontend/carla_host.py +++ b/source/frontend/carla_host.py @@ -1546,7 +1546,6 @@ class HostWindow(QMainWindow): pitem.getWidget().setInternalParameter(PARAMETER_BALANCE_LEFT, -1.0) pitem.getWidget().setInternalParameter(PARAMETER_BALANCE_RIGHT, 1.0) pitem.getWidget().setInternalParameter(PARAMETER_PANNING, 0.0) - pitem.getWidget().setInternalParameter(PARAMETER_FORTH, 0.0) @pyqtSlot() def slot_pluginsChangeSkin(self): diff --git a/source/frontend/carla_host_control.py b/source/frontend/carla_host_control.py index 95f4d2123..8df839d3a 100755 --- a/source/frontend/carla_host_control.py +++ b/source/frontend/carla_host_control.py @@ -189,7 +189,6 @@ class CarlaHostOSC(CarlaHostQtPlugin): "set_balance_left", "set_balance_right", "set_panning", - "set_forth", #"set_ctrl_channel", "set_parameter_value", "set_parameter_midi_channel", @@ -427,18 +426,17 @@ class CarlaControlServerTCP(Server): pluginId, index, type_, key, value = args self.host._set_customData(pluginId, index, { 'type': type_, 'key': key, 'value': value }) - @make_method('/ctrl/iparams', 'iffffffff') + @make_method('/ctrl/iparams', 'ifffffff') def carla_iparams(self, path, args): if DEBUG: print(path, args) self.fReceivedMsgs = True - pluginId, active, drywet, volume, balLeft, balRight, pan, ctrlChan, forth = args + pluginId, active, drywet, volume, balLeft, balRight, pan, ctrlChan = args self.host._set_internalValue(pluginId, PARAMETER_ACTIVE, active) self.host._set_internalValue(pluginId, PARAMETER_DRYWET, drywet) self.host._set_internalValue(pluginId, PARAMETER_VOLUME, volume) self.host._set_internalValue(pluginId, PARAMETER_BALANCE_LEFT, balLeft) self.host._set_internalValue(pluginId, PARAMETER_BALANCE_RIGHT, balRight) self.host._set_internalValue(pluginId, PARAMETER_PANNING, pan) - self.host._set_internalValue(pluginId, PARAMETER_FORTH, forth) self.host._set_internalValue(pluginId, PARAMETER_CTRL_CHANNEL, ctrlChan) @make_method('/ctrl/resp', 'is') diff --git a/source/frontend/carla_skin.py b/source/frontend/carla_skin.py index 82f59df64..bc4a79a14 100644 --- a/source/frontend/carla_skin.py +++ b/source/frontend/carla_skin.py @@ -798,14 +798,6 @@ class AbstractPluginSlot(QFrame, PluginEditParentMeta): self.fParameterList.append([PARAMETER_PANNING, widget]) self.w_knobs_right.layout().addWidget(widget) - if (self.fPluginInfo['hints'] & PLUGIN_CAN_FORTH) != 0: - if widget.getTweak('ShowForth', 0): - widget = ScalableDial(self, PARAMETER_FORTH, 100, 0.0, -1.0, 1.0, "Forth", skinNum * 16 + ScalableDial.CUSTOM_PAINT_MODE_CARLA_FORTH, -1, "%", self.fSkinStyle, whiteLabels, self.fTweaks) - - self.fParameterList.append([PARAMETER_FORTH, widget]) - self.w_knobs_right.layout().addWidget(widget) - - for paramIndex, paramWidget in self.fParameterList: if not paramWidget.fIsOutput: paramWidget.customContextMenuRequested.connect(self.slot_knobCustomMenu) @@ -901,10 +893,6 @@ class AbstractPluginSlot(QFrame, PluginEditParentMeta): if (self.fPluginInfo['hints'] & PLUGIN_CAN_PANNING) == 0: return self.host.set_panning(self.fPluginId, value) - elif parameterId == PARAMETER_FORTH: - if (self.fPluginInfo['hints'] & PLUGIN_CAN_FORTH) == 0: return - self.host.set_forth(self.fPluginId, value) - elif parameterId == PARAMETER_CTRL_CHANNEL: self.host.set_ctrl_channel(self.fPluginId, value) diff --git a/source/frontend/carla_widgets.py b/source/frontend/carla_widgets.py index 9fddbb6aa..3a8e2e1ee 100755 --- a/source/frontend/carla_widgets.py +++ b/source/frontend/carla_widgets.py @@ -76,7 +76,6 @@ from carla_backend import ( PLUGIN_CAN_VOLUME, PLUGIN_CAN_BALANCE, PLUGIN_CAN_PANNING, - PLUGIN_CAN_FORTH, PLUGIN_CATEGORY_SYNTH, PLUGIN_OPTION_FIXED_BUFFERS, PLUGIN_OPTION_FORCE_STEREO, @@ -95,7 +94,6 @@ from carla_backend import ( PARAMETER_BALANCE_LEFT, PARAMETER_BALANCE_RIGHT, PARAMETER_PANNING, - PARAMETER_FORTH, PARAMETER_CTRL_CHANNEL, PARAMETER_IS_ENABLED, PARAMETER_IS_AUTOMATABLE, @@ -579,9 +577,6 @@ class PluginEdit(QDialog): 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_forth = ScalableDial(self.ui.dial_forth, PARAMETER_FORTH, 100, 0, -1.0, 1.0, "Forth", ScalableDial.CUSTOM_PAINT_MODE_CARLA_FORTH) - self.ui.dial_forth.setValue(host.get_internal_parameter_value(pluginId, PARAMETER_FORTH)) - self.ui.sb_ctrl_channel.setValue(self.fControlChannel+1) self.ui.scrollArea = PixmapKeyboardHArea(self) @@ -628,7 +623,6 @@ class PluginEdit(QDialog): self.ui.dial_b_left.realValueChanged.connect(self.slot_balanceLeftChanged) self.ui.dial_b_right.realValueChanged.connect(self.slot_balanceRightChanged) self.ui.dial_pan.realValueChanged.connect(self.slot_panChanged) - self.ui.dial_forth.realValueChanged.connect(self.slot_forthChanged) self.ui.sb_ctrl_channel.valueChanged.connect(self.slot_ctrlChannelChanged) self.ui.dial_drywet.customContextMenuRequested.connect(self.slot_knobCustomMenu) @@ -763,10 +757,6 @@ class PluginEdit(QDialog): self.ui.dial_pan.setValue(self.host.get_internal_parameter_value(self.fPluginId, PARAMETER_PANNING)) self.ui.dial_pan.blockSignals(False) - self.ui.dial_forth.blockSignals(True) - self.ui.dial_forth.setValue(self.host.get_internal_parameter_value(self.fPluginId, PARAMETER_FORTH)) - self.ui.dial_forth.blockSignals(False) - self.fControlChannel = round(self.host.get_internal_parameter_value(self.fPluginId, PARAMETER_CTRL_CHANNEL)) self.ui.sb_ctrl_channel.blockSignals(True) self.ui.sb_ctrl_channel.setValue(self.fControlChannel+1) @@ -836,7 +826,6 @@ class PluginEdit(QDialog): self.ui.dial_b_left.setEnabled(pluginHints & PLUGIN_CAN_BALANCE) self.ui.dial_b_right.setEnabled(pluginHints & PLUGIN_CAN_BALANCE) self.ui.dial_pan.setEnabled(pluginHints & PLUGIN_CAN_PANNING) - self.ui.dial_forth.setEnabled(pluginHints & PLUGIN_CAN_FORTH) optsAvailable = self.fPluginInfo['optionsAvailable'] optsEnabled = self.fPluginInfo['optionsEnabled'] @@ -1219,11 +1208,6 @@ class PluginEdit(QDialog): self.ui.dial_pan.setValue(value) self.ui.dial_pan.blockSignals(False) - elif index == PARAMETER_FORTH: - self.ui.dial_forth.blockSignals(True) - self.ui.dial_forth.setValue(value) - self.ui.dial_forth.blockSignals(False) - elif index == PARAMETER_CTRL_CHANNEL: self.fControlChannel = round(value) self.ui.sb_ctrl_channel.blockSignals(True) @@ -1423,13 +1407,6 @@ class PluginEdit(QDialog): if self.fParent is not None: self.fParent.editDialogParameterValueChanged(self.fPluginId, PARAMETER_PANNING, value) - @pyqtSlot(float) - def slot_forthChanged(self, value): - self.host.set_forth(self.fPluginId, value) - - if self.fParent is not None: - self.fParent.editDialogParameterValueChanged(self.fPluginId, PARAMETER_FORTH, value) - @pyqtSlot(int) def slot_ctrlChannelChanged(self, value): self.fControlChannel = value-1 @@ -1542,7 +1519,7 @@ class PluginEdit(QDialog): menu.addSeparator() actSet = menu.addAction(self.tr("Set value...\t" + editHotKey)) - if index > PARAMETER_NULL or index not in (PARAMETER_BALANCE_LEFT, PARAMETER_BALANCE_RIGHT, PARAMETER_PANNING, PARAMETER_FORTH): + if index > PARAMETER_NULL or index not in (PARAMETER_BALANCE_LEFT, PARAMETER_BALANCE_RIGHT, PARAMETER_PANNING): menu.removeAction(actCenter) actSelected = menu.exec_(QCursor.pos()) diff --git a/source/frontend/dialogs/aboutdialog.cpp b/source/frontend/dialogs/aboutdialog.cpp index 31c7f1d00..0174472f6 100644 --- a/source/frontend/dialogs/aboutdialog.cpp +++ b/source/frontend/dialogs/aboutdialog.cpp @@ -61,7 +61,6 @@ AboutDialog::AboutDialog(QWidget* const parent, "" "/set_volume" " <f-value>" "" "/set_balance_left" " <f-value>" "" "/set_balance_right" " <f-value>" - "" "/set_forth" " <f-value>" "" "/set_panning" " <f-value>" "" "/set_parameter_value" " <i-index> <f-value>" "" "/set_parameter_midi_cc" " <i-index> <i-cc>" diff --git a/source/frontend/widgets/commondial.py b/source/frontend/widgets/commondial.py index b6d3c4010..d24e6dc92 100644 --- a/source/frontend/widgets/commondial.py +++ b/source/frontend/widgets/commondial.py @@ -20,7 +20,7 @@ elif qt_config == 6: 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, PARAMETER_FORTH +from carla_backend import PARAMETER_NULL, PARAMETER_DRYWET, PARAMETER_VOLUME, PARAMETER_BALANCE_LEFT, PARAMETER_BALANCE_RIGHT, PARAMETER_PANNING # --------------------------------------------------------------------------------------------------------------------- # Widget Class @@ -289,7 +289,7 @@ class CommonDial(QWidget): def mouseDoubleClickEvent(self, event): - if self.knobPusheable and self.fIndex in (PARAMETER_DRYWET, PARAMETER_VOLUME, PARAMETER_PANNING, PARAMETER_FORTH): # -3, -4, -7, -9 + 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: @@ -336,7 +336,7 @@ class CommonDial(QWidget): # NOTE: fLastLabel state managed @ scalabledial def knobPush(self): - if self.knobPusheable and self.fIndex in (PARAMETER_DRYWET, PARAMETER_VOLUME, PARAMETER_PANNING, PARAMETER_FORTH): # -3, -4, -7, -9 + 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 @@ -549,8 +549,6 @@ class CommonDial(QWidget): tip = "MUTE" elif self.fRealValue == 0 and self.fIndex == PARAMETER_PANNING: tip = "Center" - elif self.fRealValue == 0 and self.fIndex == PARAMETER_FORTH: - tip = "Midway" else: if self.fIndex < PARAMETER_NULL: percent = 100.0 diff --git a/source/frontend/widgets/scalabledial.py b/source/frontend/widgets/scalabledial.py index fdf795f84..33f1c83ae 100644 --- a/source/frontend/widgets/scalabledial.py +++ b/source/frontend/widgets/scalabledial.py @@ -28,7 +28,6 @@ from carla_backend import ( PARAMETER_BALANCE_LEFT, PARAMETER_BALANCE_RIGHT, PARAMETER_PANNING, - PARAMETER_FORTH, PARAMETER_MAX ) # --------------------------------------------------------------------------------------------------------------------- @@ -306,7 +305,7 @@ class ScalableDial(CommonDial): haveLed = self.getTweak('WetVolPushLed', 1) and self.fCustomPaintMode in (1, 2, 5, 6, 9, 10,) - if self.fIndex in (PARAMETER_DRYWET, PARAMETER_VOLUME, PARAMETER_PANNING, PARAMETER_FORTH): # -3, -4, -7, -9 + if self.fIndex in (PARAMETER_DRYWET, PARAMETER_VOLUME, PARAMETER_PANNING): # -3, -4, -7 if knobMuted: if self.fIndex == PARAMETER_DRYWET: self.pushLabel("Thru") diff --git a/source/rest/carla-host.cpp b/source/rest/carla-host.cpp index 7c88e37c2..d22c7d49a 100644 --- a/source/rest/carla-host.cpp +++ b/source/rest/carla-host.cpp @@ -994,20 +994,6 @@ void handle_carla_set_panning(const std::shared_ptr session) session->close(OK); } -void handle_carla_set_forth(const std::shared_ptr session) -{ - const std::shared_ptr request = session->get_request(); - - const int pluginId = std::atoi(request->get_query_parameter("pluginId").c_str()); - CARLA_SAFE_ASSERT_RETURN(pluginId >= 0,) - - const double value = std::atof(request->get_query_parameter("value").c_str()); - CARLA_SAFE_ASSERT_RETURN(value >= -1.0 && value <= 1.0,) - - carla_set_forth(pluginId, value); - session->close(OK); -} - void handle_carla_set_ctrl_channel(const std::shared_ptr session) { const std::shared_ptr request = session->get_request(); diff --git a/source/rest/rest-server.cpp b/source/rest/rest-server.cpp index 1773e4a39..8778bfc96 100644 --- a/source/rest/rest-server.cpp +++ b/source/rest/rest-server.cpp @@ -407,7 +407,6 @@ int main(int, const char**) make_resource(service, "/set_balance_left", handle_carla_set_balance_left); make_resource(service, "/set_balance_right", handle_carla_set_balance_right); make_resource(service, "/set_panning", handle_carla_set_panning); - make_resource(service, "/set_forth", handle_carla_set_forth); make_resource(service, "/set_ctrl_channel", handle_carla_set_ctrl_channel); make_resource(service, "/set_option", handle_carla_set_option); diff --git a/source/utils/CarlaBackendUtils.hpp b/source/utils/CarlaBackendUtils.hpp index 08a5b8322..59d0c3325 100644 --- a/source/utils/CarlaBackendUtils.hpp +++ b/source/utils/CarlaBackendUtils.hpp @@ -196,8 +196,6 @@ const char* InternalParameterIndex2Str(const InternalParameterIndex index) noexc return "PARAMETER_BALANCE_RIGHT"; case PARAMETER_PANNING: return "PARAMETER_PANNING"; - case PARAMETER_FORTH: - return "PARAMETER_FORTH"; case PARAMETER_CTRL_CHANNEL: return "PARAMETER_CTRL_CHANNEL"; #endif diff --git a/source/utils/CarlaStateUtils.cpp b/source/utils/CarlaStateUtils.cpp index ea0cc86d4..39b98cfaa 100644 --- a/source/utils/CarlaStateUtils.cpp +++ b/source/utils/CarlaStateUtils.cpp @@ -185,7 +185,6 @@ CarlaStateSave::CarlaStateSave() noexcept balanceLeft(-1.0f), balanceRight(1.0f), panning(0.0f), - forth(0.0f), ctrlChannel(-1), #endif currentProgramIndex(-1), @@ -244,7 +243,6 @@ void CarlaStateSave::clear() noexcept balanceLeft = -1.0f; balanceRight = 1.0f; panning = 0.0f; - forth = 0.0f; ctrlChannel = -1; #endif @@ -348,10 +346,6 @@ bool CarlaStateSave::fillFromXmlElement(const XmlElement* const xmlElement) { panning = carla_fixedValue(-1.0f, 1.0f, text.getFloatValue()); } - else if (tag == "Forth") - { - forth = carla_fixedValue(-1.0f, 1.0f, text.getFloatValue()); - } else if (tag == "ControlChannel") { if (! text.startsWithIgnoreCase("n")) @@ -633,8 +627,6 @@ void CarlaStateSave::dumpToMemoryStream(MemoryOutputStream& content) const dataXml << " " << water::String(balanceRight, 7) << "\n"; if (carla_isNotEqual(panning, 0.0f)) dataXml << " " << water::String(panning, 7) << "\n"; - if (carla_isNotEqual(forth, 0.0f)) - dataXml << " " << water::String(forth, 7) << "\n"; if (ctrlChannel < 0) dataXml << " N\n"; diff --git a/source/utils/CarlaStateUtils.hpp b/source/utils/CarlaStateUtils.hpp index a55ca7580..a420635ac 100644 --- a/source/utils/CarlaStateUtils.hpp +++ b/source/utils/CarlaStateUtils.hpp @@ -69,7 +69,6 @@ struct CarlaStateSave { float balanceLeft; float balanceRight; float panning; - float forth; int8_t ctrlChannel; #endif