From a94e47ca33c3b0e57a9d1cd6df4d2bb8fe15fd1c Mon Sep 17 00:00:00 2001 From: Tristan Tarrant Date: Sun, 14 May 2023 17:43:10 +0200 Subject: [PATCH 01/31] Re-enable MIDI program change switching after last web session is closed Signed-off-by: falkTX --- mod/host.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/mod/host.py b/mod/host.py index ced4be68..5965429e 100644 --- a/mod/host.py +++ b/mod/host.py @@ -1503,7 +1503,7 @@ def initialize_hmi(self, uiConnected, callback): for i in range(startIndex, endIndex): initial_state_data += ' %s %d' % (normalize_for_hw(pedalboards[i]['title']), i + self.pedalboard_index_offset) - def cb_migi_pb_prgch(_): + def cb_midi_pb_prgch(_): midi_pb_prgch = self.profile.get_midi_prgch_channel("pedalboard") if midi_pb_prgch >= 1 and midi_pb_prgch <= 16: self.send_notmodified("monitor_midi_program %d 1" % (midi_pb_prgch-1), @@ -1512,19 +1512,19 @@ def cb_migi_pb_prgch(_): callback(True) def cb_footswitches(_): - self.setNavigateWithFootswitches(True, cb_migi_pb_prgch) + self.setNavigateWithFootswitches(True, cb_midi_pb_prgch) def cb_set_initial_state(_): - cb = cb_footswitches if self.isBankFootswitchNavigationOn() else cb_migi_pb_prgch + cb = cb_footswitches if self.isBankFootswitchNavigationOn() else cb_midi_pb_prgch self.hmi.initial_state(initial_state_data, cb) if self.hmi.initialized: if self.descriptor.get("hmi_bank_navigation", False): self.setNavigateWithFootswitches(False, cb_set_initial_state) else: - self.hmi.initial_state(initial_state_data, cb_migi_pb_prgch) + self.hmi.initial_state(initial_state_data, cb_midi_pb_prgch) else: - cb_migi_pb_prgch(True) + cb_midi_pb_prgch(True) def start_session(self, callback): midi_pb_prgch, midi_ss_prgch = self.profile.get_midi_prgch_channels() @@ -1572,7 +1572,12 @@ def end_session(self, callback): self.send_output_data_ready(None, None) if not self.hmi.initialized: - callback(True) + # typically initialize_hmi takes care of this but without HMI we need to do it manually + midi_pb_prgch, midi_ss_prgch = self.profile.get_midi_prgch_channels() + if midi_pb_prgch >= 1 and midi_pb_prgch <= 16: + self.send_notmodified("monitor_midi_program %d 1" % (midi_pb_prgch-1), callback) + else: + callback(True) return if not self.hmi.connected: callback(True) From b2056cb4b65d7f6f3cbb7cc82613fae2ba91430a Mon Sep 17 00:00:00 2001 From: falkTX Date: Sun, 3 Dec 2023 17:47:46 +0100 Subject: [PATCH 02/31] No not connect IOs on default pedalboard Signed-off-by: falkTX --- default.pedalboard/manifest.ttl | 10 ---------- default.pedalboard/screenshot.png | Bin 36107 -> 82642 bytes default.pedalboard/thumbnail.png | Bin 1325 -> 1338 bytes 3 files changed, 10 deletions(-) diff --git a/default.pedalboard/manifest.ttl b/default.pedalboard/manifest.ttl index c3271863..fcf27c15 100644 --- a/default.pedalboard/manifest.ttl +++ b/default.pedalboard/manifest.ttl @@ -4,14 +4,6 @@ @prefix lv2: . @prefix pedal: . -_:b1 - ingen:tail ; - ingen:head . - -_:b2 - ingen:tail ; - ingen:head . - lv2:index 0 ; lv2:name "Capture 1" ; @@ -56,8 +48,6 @@ _:b2 pedal:screenshot ; pedal:thumbnail ; ingen:polyphony 1 ; - ingen:arc _:b1 , - _:b2 ; lv2:port , , , diff --git a/default.pedalboard/screenshot.png b/default.pedalboard/screenshot.png index 2e600ba39bdfde0afc1c2f345f8bdf1b62200822..8889274b067d2417a3d638572d3abf5260a5a9ee 100644 GIT binary patch literal 82642 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 Y#@DlXtwPzT&-PD#<$AQ_u;2Cn0CCkZ5C8xG literal 36107 zcmeFad010-+W#N5*4B-gaiNMz)d(tE1=%-8ixNSoVnEik$|8$^MD`_EN1b93Ndy{} zWK@`T}}Nbz<#!vpcnE{m%6$6lxR3`rC6TloI^<*@Z7YhySU3qvnqMg+Got{{{TSeBt>| z`1{LXYiB$PwP6eL&ni@U=2s}x*C@=le?K3QIr?nXD)>nJJvn3i3*=ir{d}^(CkuSC zz$XiQvcM+`e6qkN3w*M`CkuSCz$XiQvcM+`{I9k^Jg;K@E7J*SwV3KNp7}sEd&-pMC&iJy`keN}KbzJOjaQ62qOOjRsJ5Vv6#+t>dX zyzSqH--s2V{h*?-xnR!iGTC^&7bey6&l99Gu+%8(x^44 zv-G>psI%L@Y|m-&6Kc)><4fSz$%d_sbB+7^x52puZ`7$VMgG^W9aYwLv*!Hq$G0a> zo}8Xzva3WlRF}RdZp_)HFd!aP#@!(xb=rUb^miUA+KQUb-G)+gg1Qr$R2n=>Folw?2kAUb64|e;B8TligB9(H!C@^<^j z+YMKqr@vujrjkA9&z@_iuCa1yQdk$>;_s5anDr0myhn`0Fc@tlBm-Xk2J{KQ1t0@M7D@b9mxxI8UrY6vV$m^no_N_hY3!HXbSS5WUgPi>n=|H!z*5d6GqGKah5UAb<>eZ@12 z-mY6(@_tn9{qk<*^iC|whi^brQz!rpA^+c?A^ zw>R%@$UT!mMAs#$a^}YPB(#!(l&oCnwACu%p^5vpS|@{mti@A=h__1qkz%`}D<42a z!NKt|;kcZ>rB`u^HrzsMpwptR$|Lyv#o>ZRbmV&~B_b;@=XA!KiQA*zdR1lwAi~j{k~V^Rg@cpTO8P1{@A7TF!JPx1LY(1#&muE z>DHvU8HIV+=!L$iyW!a)MU-q!|J)k$n1x`+#}|^bv3iK9WHOm^vGPvDfw-^pA5$^Rs*bn;$UkO!$DvvC$3C|Oo-AWvj_pLFHbv7-d&7#Vq4Jk%fFJ2)=wVnPI z@AbR;l+FqlFzFcTyCC&D(Z3HfKQJ(m+pJ*_Smr0Y1vgp5Za8+N_^S@>$o#o$F)?PQ z;p4VJK|z76!PGn5?J9v~585e#O~s7qbA;b$dI}~vYM56N=UH)UFDK&7jlI*C1oZ?%iYOU**|lH9Wn-FS=BBH`9_}A)Om* ze7+;Db!|uSz3LgZw;?NRtQ%d=mN69u%)Tgp;y}GRwds__bn?=as^Tj0<_E>RQ-O#b+*WQ} z{fWQ6JU7D+z1cO)==5`HR_f3d{9+GBW3@Kq=TxGlZp=V;;=mo;(p#&sA;wYzW56FL zb)i=;4#&twaO_{rW$P6Es_4k8(cLYfcbsBX(&%MdQ8ytflp~dTH()BWB6`s zt>VbX^Uqr5uFd1FbXvqYg)5~;9$Z~KcHOL6{>+h^?s9j|B8_Z|o9RoYUoz(Er$5Q8-x8ZNP;o;l zju1RI#_r-=J3{t}QXL6cC;nEb%Qp^3oG_a}=wjfgH*emwwY9xQ8N9hc+2ll=PCCjh z)e9WGuCXyWKR>_ak6y8e6TbefJz=qZ$IhL@=bImC*n79+8zM5oHk33Ap5>+9# zsvC#@fYvczW#mPcpR^}tJ9gA4<`RHwlWQm5*y- zuL|4?fm_knX^9-kmiDrUQkJ(?u^5j@Y0?+zI(43N*Ig9>9sx65!IoAaPK$`3|Y$ZdLrH>Vu)+jZ60u-E}kc5KYdN1xP>3&KJF#?_$3YMs4HCM zbGLBK!BgkMex7LN6(o5DEPBpSVowo0>Hcb*K!wfBtzuN#3tibcO%a?z`w8dLz0v2HE0$_6NBV z{9h;~<@n>1B^~Ds8b^oN#?Sq!4R?PU!7tF%LoUh2A|K2L(Zm)(O+Dt*E=Z+lJT!xdN{uJc#Joo_mH@cJ)$V#n{t;?vg_j1ckIXQY=Jc za5$VOQ+aI-Hu=1laJaYd9aaO?S9bN?Hi05wJci#W*}pP)i_nv_yka89ZrrrVy;auV z|Gi%+tEtPIgcf`G|2UqrD!5|5ELl~O+!D{*q+;Ot{MM#OPIMnBQN>U=#}DG9YJtb> z?d{d^vsZR(7XIGKYQmYPsW75kU)-hGgof&g-<8fBjly@PUoke0MXUHQ2Pd&fqTEWo zcJk>~>_Er$)|S$1W1}s{x9f%nTIAV1jl&(tpLpzLA@d1G< z?@V3fx)i1&E9bNRBF%OZ8jJOUbZE<#Er%M5@W+dJO)pbJ4;)H#NGIGqKI`k5!JqfCP~Yx80JF|L=gBqJ|iSvCRQ?uEh=o#^~9yjwPM1V!nwTWa?%d{8A z%r|%y7M~<)o)_?XRj&u@=D&) zny9qAGTO^0S2!hId%kKY#T$BiAL%oXnz>BOZoPbsUch7;LLTYv?w-#b85$cK+vR_B zXBOV$Fs#&*F7*=4`{L0aFH{1iMdB+e<_ z)JIR;alE|inwOQMuzQ=~z#pCKNc%7;Zsymngj8E!3bx2&SP;=e;~F8I5`Wz^mz}-? z5)qYiea$)|n*a1lV|A=lyPOKX=I6vjFZdT=TJy29HIE)W(pevW=iy6u`|PqzQsDjH zWf^BC<8_y_Sf1hq{h{9Ej*9JVmMmS8y0)f8DcaYw+R=t$OJrq}2X1<5E9?HUDT{N# zdSv0%n=?CH=6Ir_Yg%LLqJ&N4(H@qb)mcyQ#1Jg*P*G{@=s1rA;Q3khREsPcZ9DrP z<98+wS*2s`6QV{$F4*X1`Adr>I&SlSyuK(@jcap*JSFn|gWE+RgDJgAn>KmTk7SS~ zl=EX>)^z?39(3+&UTbS>Q&SU^3~cS~eZs;FVA+zhvYKaRu8Celj#6Ikaw3HPJVaFI z&tt{mwi`5MYo_loW1Em36*`8+8DWOW)L##5!L-?`k;3HVCaxx2-3)IcYi5rNX|9Za z7@Hfi*Nb+v{pHjt8? zIaBZI@MpS0?{YXDL{Yb;R^s96HVCK5Xye$$Yf({ACO?FLbKSh#)PK+|*T2v@oNk;JHLP=FRRkKNtE)>KA0LMh8W!GtFR~9c)*!<$sO4RfC8gMh zlS`w)0t@eM-^S~5_K~}38FQn|`Egg*mN1yMWbd8Ev7;9t(MG-njz5|KHl9KI&Q^}0 z$}}6hKI?~~4;K4p91>O4=UnOjw+YDi?9FG-2UbgjKU<2sZ&nK$Uw!9qFbRS6G-bY0 z-RkVXTz$?iD-!VZJbl#LdG-osNMeZ zm~O6Xj;&sEPw?|ikps!6LV82FgRqA{I`RIq8YF^+Ao=y{3*EgFR$JnuqoTCe$Ezfs zTwGiXd6#JD4PS?(10ez@_H4SKBd1S6SG6aaO~2fqmE5#{|Nb3&_O!rzi+E#_x_$1! z1T+Tnn!Z_j_yLT3U_>Fa8VSXMKT;;5n~RdQPN=k9J_>ulHLLj*w~q?48hgWF)W(%W zYBx*fzJ49ma37X>5N_03HE!X}F0+Vlz{r36w8c2M|5$ErZb*O4^~%ZLH`x6BioJcF zxvX6=p+lc}7x87G27S8^N)X0hSA4NH;jw##0TN7Y-@aYf$mkbzmTyg?I!akpu~1jQ z53JCd7Z3s)b^n0h^9!s!o-|7s%%jUk@?Kp@e9ht$qH z779JZ6dheSH}gUz2+D_$_cf3AR)}9;N*b(-8?6gjn0yn^S80sJx_40IEjk2AXvA@w zRl$1AG1E4x%_56=R?IYsiac?o1mM|Q9*Q9%@r7JE!D&d*5bIfr^^o@@RSku#1aP@S zBO@c4y#fI>BdFT=wvuW~1*C?E=kn^R+-w;71gcI3nCS_5FS~JcG=Mnp>cNSji3!dc zC9`|Y8HS?k>-M~aDi{?VDVK=EPm*FaF&>Li1=$Cc*YwjGCgkH2dvp@^VX@ddQa54s z;e>~eX*7?mwfToTdE7TsD~(f0cU+Pu2p)4sRJ9tX(;psmL=QRlB-9p0-IU&?(&M~* zcG_6tOk{@6e)jC8K^<@mB1*LOnl)=AP#w-HbY%)%1&fY8m&c#oScm7j7haC=a>OP3 zOv*|aEPVwtSt612#7W1HN8D^E$b>RMOSE)x5ea4?!pzrORhDO79x*@OtDLw2N^}K| zUXYn1r9)Dt(bawSma=P(vcIhEKlx}@t4bl8;84t?!NJhk@fRv3Z{ zfl~2lEEdVZM41zjRmueJ7A#hubROGj61+p|ca>{kgHJ}%W+bG7lp1z@{!=Za zx&XC5lRH7Yq5}}@8ix#6LlG(s@yR1aB7Jtg&2FRMuQAh;&y~Wf|Do&=v3CNZBh~7^B?{j%aG5`{&AfO##rVoC0Fbyw=T8m|U3ag!dbqcadSgO6_uFhKk| znpfiM0EMUwcxk*nJqO;R%FMTy9ZcA`c{6yqtyalJn#iKdvOc%VI3#=V`>xT5=856I zA>M{K(8I#RD}*67H8*=*ytq$KPmlb|FE4v~+(Z{l9qouyKgS75?FZgl6W^fo+SkVy zRF(!}r!FmwZm6*Plc6q$uC6(ivUv^%kxgeW?*;36Kb<%m* zJ-nP#P1EKp+uPf-y5_;Z79tyCi%wl#9e%R^+0mVa`79(L1J6M6+25z(RlxE6gxjfZxPsm;QaQhOvZ=6^0)z2`8w|T3=!w%Jxkxe_d+amcyta`}h zE5vPg*b^;9WefR?@X#&-EFaT5P@WT56U~+;p9{n{XcLhG3BPJbx_cB+Eg%i>_VIDR z7BVTuc(qHHE@k=FV%oqE4Mk1$By}WA8{?;`?K8}1hBp?CvCG?rQZHQB9%r5A`UAVK z`)r_TQ`VUf?fM630tCxwJzrU}7F8I{qGGk!;Ib*2AZ*oqx zyC4fyxrF~8kN=E!vg32ITbzHl1;hVpp>ARDoEtIJ_spt24d?iar`Pm5wZ~Mq;nVEp zSF%G@#m-TVYXl1~3@yl>*wk_VuQBuRU=^Zg&z?aji-sgg-JW9WqV&+*58SDz%k|Oo zF3AD=3vY8NtCzgfQ9ZUJdpq08nPhbF9ZMZg-%^{Zl#Qs(x=)*NM}87v4=nDB}G%uSo0n)J(d#p)IJ zTPg3}a+Ckp;Anxh#%k@5$QxG(um2icap?+9aolBIk6l)((kFyGv`Vw_+G@yEiWYLi z4)su0n^lxqo9j^S^XI(R$MdCw{G5+kgAuZ-^APAIjBAkP(sTaR6pvwiMN!Ad({=M< z2Zlf(kX^96t3$E5S0T@ui1C>vb7F*ZC4O(h^n30SGFV2LvBm#jr5OEEA{AZPfQEy#VnGJ#)-n?L`i$1N1wcX_+<@LLm&X^MJ$xF;c3D9 zlYLQJ@Pbh;0$=_6XVS5A{MemJiL`6*c_d#tu33==p?RSX?3<-7h1-y}HEgwoL9rT0 z_0Q$ek2<@rySW#wEa1g+yIu3IR2}sWoUe38P5-_W_-(SQl_IZjb0Ut9@tGpn$Mb299^J*@wnn>4HP&t3 zW&No2GF#I2WlhOe%huVSOdE^3du_33>{UJ^NDFd_c4#@#>GT#k5(C8Si+qzzcA8st z=gq_k$#jsnZ?@iw)1iX2vM9{f%h|jDZ)mpf*s%jMHNuvUbl$d@d9XBKFCXZ&I_q}f z!UdzO0XJejS`Q;69o=`bSy4TR>(ejGSJ*td&GGtyzRgC_!jAp&A+|LGn9Hl9x)q#8j3No>Q(w z8~D`XHf0+y@1d8I(-aM-+=7CFEvWgK8Msm5=u5X;Wz`>24qKIb%+&@&DzI!vY^Neh zloc-TWk404aIY(T>U1pQvXs#nzLKw-Jn4|rnP|}F7o`kYlp5*iI9nk%o9%EobnDiw zI2w&M&g1c1h%B`7cIHq?uA-}W>uUDk@2&Q+!wx#8k9W(5^WTaBW!4@GVuyV9%WG?> zDzb{m;ru*MFF@j2US1A)-Cf!3Cov2RwQJYG zgoiu`B=H7?XP7SmRwBo(c_s>qg)&!RU`VZL-_}`x@)1KUeJ~p8XBs9aN;)clE%5_ zu$*o;FM8R%XZ5o~F7KO<;Rri@NZ1o9AyLg!GvTv7ae)?F3hsJh@u9i8$qKnZX`qg< zR40CQLdmN+T|X_B0O>m9(#D~~=ZRZEQuGqSU58XJxlpi_!6tu;TG`s3>~`%NHfT@( zyz;jNJUQp9vQbRP%xu+D!&FEWsz>KHD`hs3TiaQ$GVS!5(e-+qUG>mUfY-AP7UY-L zm7sQZz1ZL3s3UrRp;$R2@X!-GVtyCBRbrK#?`BQRw2U=Dt^p)9xX#b|ADv2qd;=<1 zJs^cxPN86NG!1g$$m?dTs?UXI6>S|#b~K2+f8 z>3Pz!O6>dUQ(wh8#09kX^%VI^CM@QasZGTki{&f%;I^QCjdWF?1Z6g#z(tG}X%D|3 zlo=!C#sY#kNZvdoonRC8DJO--8PiIHn>aaGf~sv>7G#DKYLbgY_UVGZtlS*>HyuZ1 z&g5-Vc#sCV(JI6l;Q>l>^-`iLFH1#HH^`ROSh5eO+htlqX$BEJY>8J_Q#%(8m3B*G zyt8uX_guLc^4CW`Etx#(Km<{!VN})7_FA;T_hwCE?N|(PKL$Dn11Yy_SKfT(ti&$G%2bK|=Y zT>J+0=>6_>R(}uBD!Hbi-HR<-z2V7U&OURRQa5V;=evU&BR2oz*lLWm;_8QJG(PJR z%4`(7uKh?7fh)`K3iWVzRPMx#tb3l3-dGjhi4Aq{J+BnYMlDr`#AQ+DtEXe}n_e#{ zuejCk*X=+NTxMn`!=G}0{fz49lNd57zZR>4XU((DzOTv;ERepq9A3>H(&JXGd^YjB zJyDe`t49mb=XX2ZPCPsrv@IT2PXSQ1@8T0v*5q90RLA`@aaoGS>TfeK3U+C1wL8e zlLbCm;FASDS>TfeK3U*@jRjOkd;RaZiGJAge!gk9UfCAZ)%{=m?dzI5whjrJHd_yz zej48BWp$U)U3xG%$Ee4ehJ8BF`#Pm9x1sO+x%}+I`LCT#Qtv0`Bp5t-d$BgPHpR>M zeq`d6hOI$gTYn$?;QBK!?|AYvx2W6-4o}s*R}Y2x>9b=A+0=Ejo!nkgCW9l1niLnV ztY-YLvF)cD`M+WT?X-Ou!CN=2y9kJzv9hTfZ{wtyaR=?*=;36Ji7MWT(pEmDXY=>| zxsOT4Hccf3^x`}+`Fs0mBlL>#^kuY`;%P;cm#ltfen(wd$K`J;ufJ@om5Xy<7%zxG z+y>CDsBQvehd)8T!MqbRcqyJf#48vBv~y|1Mf=pY-&Y=6Q|r%^&t<0!x~Dik4RDtjGV&e-$bJyr{CHR;jCN zs(jbR-v-rU+(Sl1g(;{BK_z1iVq@s!;v@}D(@>y4Q2#ygCR&UsZ(qOaqYFu+TWdV$ z2cq^YsVfGk1MgcONt%2tNHiLJvy(@pSJ-$DxpqsmvW{QgKRy%6RihVIeZSIzWoD^95f!9s+>u!*$J?RGKx_`$6P=hh8wkvU z3HUq8j(0w~#Ihxm$UdB`6DO;s(Hv#J1L3S_|MEXc++B^^{`xVAYjf6ZvfB2Xwx(tW zai&BizM^|of2lur?tm}aSD2#`&&PiJY&GW38TW=!nB?pyKzLQdwP`j&w*<%$sg1Ga zGKp)DkmkH=J6G z!uzXpw@=e6GP=ECF6;_u!mynQ6LTe`8W8B(5x4o?jN?BO%w4o1F1}U%@}s9$GavTD z0@MAi>2F52QWLi!N93(4qtWb83rKH9A%-53M=!Lep`|~F@FYT|T}H1KoD{=2cPgN` z*x#FLxj`>blQ-v=ykk~UrU;5Ftt9AO7T_ypnL70~2wY|c3IXmch~SoN30SP;cjfBu z2>sXBl_N?|XmOm!hQ^^55;W-VWr#>GOeNT2u{DXlFrCKEL>Fi;JfxTWSR9l)8BiMF zVUe)m?#c%}dBZ_7BQ430w1H2Mw)x52W1IE4j~CycvP|zR0HhWAB@$B^$4qRCoDK*c z&9#6XHz0P7lPyBKHq3E+WTdgv373WTFZwD~cFf|4e9;0o-N(6dh6oz>%2-Q8=^4s( zPgAEv?6X{YZi#%2=erj`1VM|)8$?R&AX<3O3{|AZ-fGF?DrBk3B0u;B{SOu4N+ocGRk{~GV(;xxm1ByG=or0ja(1M31 z%+NB5>jo%pK5)klD?rh zkC76##I)7J1lnq#%*qhp668yKW{`c`;bwQxyCdq!B2A7G3f#qGSEhD>V`0MrY?Q2a6)0en0qlj|qUdGvehd+TY5~q>4buvA_4QK_(X^XmwNNSa!Q8F6 z`T6j+CJ?M4FT$c!Dt^kW(o!)9c;E4j07UC7V7^IcCr<%9>_fV*+S5vJ6u79o>aBS5 z`pG4KQU}TLA0}z)Wg1H>TRxRo_pg?!I=&80&W;WV33->hO@&?-BHM;gI)s$#7rl>s zS22)Nln074CWjbpZRSPUiravxxR-Uwn62vDVEmo_V^h><|5wzP|6D37R+K zDzw{OWTh@+4U+3FLKQIvj;49-ExoV&hXuFLH4QC!tIUq@n43w98%molWyESBBx|*N zmH~E=-BwJhszqL%E7{G}(v}?2cDs5A71JAPC`2G)xLOAW7Z1Rb9VHw{G#F`7>Y_E) z6XaB}sw_6x&yQBTFfiZ=nvEjl-5+rkGz@nZI5fKXm{ka$n6bv*n6U;Qm^~e054x$H zYjoQ@Zh0_|~MA&yODeo3I$Zg@lt0JJ|!H9K=a0?GTD|4RJPw&W$l17d3^}6L4XO(j37^ z#6Q``>_ecIJWJUSBHpJi{Go1Ahg>^cBsVf@jKCrB_q2pR?W5LlEnhX1=B7Tc;&`dG zi?U4HLeW0uC-qVtA|Fm9$AnKlFzcHSw%dA2&)rr??yEDoM4-HM^ETzuacm2~zrOnq z8h36QG)h@Sfp}*;plm<{sHv;#>Fam;_LzgaQ~&!C^z=;t-^n^Je&6s8NK1#!&1PwN zKGh_g+tu*RSK>@e?fdTkRA47OKXy6LXzCG$7_~hlLfY<3Dkb~cip&cLnpTDCR^iiW zk}yYAcPhodaVc0bj>Z(TDRnRQ)NfK3q@aPdvLVLE#Z=A*N&u+a={HIQBP0S0soNbs zprn{o;+Td3Dy<$l^m+e%uSFGeK-8QYL8Fdv#_9`C7{I_`@q;DigrtBv1Nup)+=as5ARJ;EaDXY zfn{%Qp%M0DI^RC_Fy@?V6J-xB{A(vhZx#aJGSn2&^6_nK;+wbm0jdI0yVLIa*E3V2 z#i8tIM+Od< zvZ6*`96iT@tza5?3|`Jca3`X*@#^r50GbBzYz_+Mtz>=PzbyCG*~V>YZbk?h(2&(H zqeDO)W*r-2g}!svtK8eXJF)VLOlyawQ7Zk6mC(V4r&(N~$lb_@J|AprjB4Ja%k zdqN~~l?d{%JRJlX#NqF;6-7_TKCI_75xAS-vZ^1zS+_Y!9=?w6QLk#eVlCBV)Q8`y z&N`D};C1=(`=S<5U*~rUbzL*g5S!8pKkwbJre!zmXcvFnsuAo>CAaH;EK;X)x^P>MIaKXqx z;7&8Z!2v4>PvCzNxPSjZ;3AMBjBgOaz>%!m2!YG{K;T9VEN1e*0Rk5r-D9nXKecm6 z&`T1jB#CI9PCkn--O^om0`|*m$~G-TeIRf(+W&*VjaQ9&{{TSiY`_telti)#0s(Q- zd5}aYnN1#}SIu>UM9+s3sfK7TBQ1}+Chx=YZ0)NqUpZ}@2BQ64M#23>9D)W@Ftc9K zvU1Jz9i3B>dEL5wTMsZTo`^-}HMPv^7%UUGIx46*KpS;oxzK1KFdGM$gBWp!pa}#{ zdqAOu1ZWVi59LukQq8k$i07?zE28$fyZ7?+rB9pOt{TijhKBdePTr{GDIC6Jj2E5{ zD=gcK_Yt*y^{dZ=$Dv);#G8H=Fdrg0N+jluS9(Xh%kZZ^bPQwz){u~CE6W;CA|spaA}@3I4qhZWv4}GM&{W0A zjV#$RyVYsCanUTft9#7}miPFrCe2&nN+32x`xaW;!T+Q`e2q$bM?u&jyR*zju5YjE;u~EqC&5EcQHBA zZ?C(5dC@)%d~?DGev~G|Bxj$d?<`j{cFjIwL?t=xK25wfzl}9L+DW$2N<2EfBiX8m}|ok2(x41F;nRM6T+8 zcn}$X0kI;o=X*J8>!X-!U=~<09=ylsXYnGD2oK}B#h z%rb15mzTH27Reido1O*{w>rX~fvrHnipZZ?tS;F`Z$1(>w;il-=yc^6uTZn; zXguk-X|qriH!cd{n9sX)NggO%SFe=dMvUxOlss5k;wWO|y=-YXNbj-)k}O+85z$5C zm?<~YMz2|UZTvdK>$pA*s7JUtD7b*9iRoXX`Vx2u@g>W-OBf<(BXczyk#~tswja|M zQ0HWhhuUrRxHv*A-~-8S(PO2?SKodMfw9S|e!veuOxMsH>zRn$RAX*#4qN`8IPUXJ zhMth-vn0X{`~-sIT6Ww-aNIK=aNN^EFe^f!0;G*h=>n45VXGA}8-@Z*L~$#KXu|ZY zC80IS9Si>GCd6O?p5j;Ad|_zP&Yk6tSIW|BT+dc4&6fUhD#NEOGf4SamqTvMS=7SQ z+Fe7>l`J!1Hv~KGG7Zy2CJey>h3G#GaK9f8eaCNaucyd|6!U3m(WV~9NA4EXZtiyt zl6{ZNmx3aekmo~fhUDq>a1X&d`ZmN;-e6!Zt_zYhm3AcC|U|h~1iMoXN)#Ae+4b|NaV7ePu>ZZMU}zto|Q07;0*C- z+Nnz1?{32FL^G1-J`8^9o${$?II=Fr9CPLB%4wXyX|`j=?Ths5yGxEQ!?+!*m#Vh< z-b7&BklKLU`OTic%}2ZUv{2%te8G6h(jJQ#BM3%a$Dt+-78`tUd(ZIEwU#g|av2fH z?re~{xb8t|JHXr+N<9R0Fp&b8a{$E@aBJxL7l|sxP})Y~IB@f#b@BXCB=QIj{s!p! zg9+e!>i|2tKzs}IdFQ%fZ>hihymG`G&xeGDiXl==L=^1W$fONGVqk0w5WyyO-9HSX zcW~a%yGbu>PD1an3(+k^1*h0-Kp7I7)XA)u9~V*Hw;V9O@AzOi9qP9g({I0@uwi}k zwOe-QzRF8RoXkFA7QdQXJBFHsPzmUL8trtI2NJ))v^2=80nP0*HZV5kv4lvN$U9!ayJ%fhHG-fcCcL(RRiwizZNp{(HJ9$w-KM)dbv~xWBv4D z;*>4%rlzAdfqiVu{|7#uyYg9P|rd@dj$6sQ3)_fC~LT99-`;s-(Hn_n~`)_ z!s9{}haIZs$uMVU;9D2{_U3_y7x|qm1keBR?shDoq8LxBR z^T@GEOTz3QEZbRn{WhnWY^F5FF3`uG1H*9;Sb;{%w1_oD|feO6?eRHwbxA>I8T&e?xJ_NnuR^7YVIs* zwbeNK5&7lVp?&U3b_#D4)0UMpIZ^vrd^F(rv>SCH|C+e4`SF>ei&Nrp%?-+yv$OP; z#UzWx%RbBF@}TYxGb+5;zee%)r&)zQ8=3wwauFtkz(^OEO=@QjR0;TZlPmvCj;P_cvV7*mr@ZznEqW*b>)Zm}XS`I4^tgSB(ds{NEn zhE=~U)42UGg~bpxcps^kA8=-)4YqnQ7SUEgi0G%?xzu1o$U?h zy6|!0iCRBpZi^NjNYyU16WZ5s74t+5W1vF2^gT1nzUr5eZf;lmdR=buc{A5t7BLGa z;N-Bi`4iE|2nt}%)nSC(pkz}Gcsc13)MOeMv19&?H;P2tMG`N=?z9}#11#dMX#x^v&U0A$6}!*u0zt~R@$ z6EIS+357c?6nA=5R7#l1UHB#+U&iOvQ&6JSt*2?4bB__n-_<24bf9!m`1vx83nNnP zT~u1>E)fk9TOs=ct&YmFPePvJ<4{kt_~&vjN3n$QL=Q3o;RyG_QY&Sqhw_P zeNtAc0x+&lXyKe&PKqLc&B()k!A}$4xq=((X58LDebl?Ur$rQ&x6PBDGi$5otG`O= zz>cpVIWOgUWI##e19}d@%QUNGXS`2H++?^x;nu0+E%s-^^GY%GDjEyX4-Sih&PNw_ zzx#cX@t2h-Q_Xz1u^!nfb^Q+{$1lp9V(fpR!Yc^alKee>^6|S&u{tl~0*I=Vzv;(||{X{wj^8N$Id# z&vcFVQC7|u)t+Hp3CcwUM|)Ukn|(A9YGfiOgxctO4R=O5VN)oHiprlDYdGh@bnk&d z+{n0NI`m!JQ85sf$@0Rh#aW%moHBmf=?zSW=VOyWdZKkc0?lWF4en(>{K#J6p;1A* zV8|D1G?t$ubj3}$`AVI;$6hp^3Mb0ZiGsyK!Qu_r79SfiRzX7;2H93fyIg~+f}e#Q zE>C89TpG`i$yct=K3cc&t9?Q8n33s_zPX+(FHXovA#)Bo``L~KrKUGeHyNhSu4UqXwNa4yd(gM%F@tbWP>~L7 zP>J_F6!&;!OZa`S(gHHgn)bE}$3F)-S00Qv2AV8O0(r!LvAFYV?>rN%6Aubrp3C{@ zx`D+t3wqRQ4YfNMYk~{`dCQF(VF~EN!(*by2*09u;^1g$3*6hz_ckO|AO>kXnec{f)L@J@kY7gXvkTCzSM>z zy91ChLA=a@3Z@NX1Rp(&)!o+x6toO;i})B*RLx)n;j!2P`Ha0{#s;TYnE;`EH@{H+> zmAF-9>v`Y7lPbPm{|)0pbobvd?!$k>xT5WK*IdTfvzlwK3qqbP!?+Bi3#Ln*TWUEy z)5(>S%IfD#x$9Acz?lgo+ea=hI=a2HGX>JR0n4O8SHLF`j}}~aJUSJJX2D3Dtqs8U>?hRA=t2ck5S50{gmxdj`!YKbUpY$B24J|uh;SO ze%M%lR)lK#d~Tmp{T(NrzRB3sDE;w4d|8y5#w|xrno&mcey(@mEO};DU+?Hs3zENm zuErYVjfa64NP8Tn)rlWcgP=l}K^iEg%0_*VTvEn>SoaVzZRjzME$3QJCnudpD!_`+ z){m4alYUx3D0@@c+m4#_hF5t|r{xJ2>J2jmkApX4{lBmUPRKEFl z12Y3dwg#MBilfGaY+gVu($s^2S^&C3fn^_*DUi8bA)Zz=9{qdTn)0~WN9y;1?ON2o`4`6b+m~IKLE?%5h$OY6&sMx55#1)D)&gOI)nx#1|MiYqRXjf86Q~Cig`>a{6cKhWM=fuDO-B`=@5^TRw58^sJR^*Wby$@Czy_FJBH^ zSn=%}aKkldQCx3lO5U1}S?`y>@(Q(|xVT@f(&%Ko>P!#qJ-@rw#mk)C!nCir`}(&f zSHD?LO))xIvjzQlkB1x#+e=hmM$5eY@c#5$-||UsJ^!_l+-df<__bxl?{BK` zt6NS0c1~vQ`NZ}h>*k%9nDtR*^>57*&{oVfm@6RZa(GVC7fzc2c{2@@Nkm<(2z*Fn# z;utdcT|4T+(GVC7fzc2c4S~@R7!85Z5Eu=C!5jipzde`$8W(W5@c;U{4XEQbKT?H@PUiRf6Xxyog+RV?|HiVxvXYMo$;Vkcv5P?{3VV94*2A zp!l4)(~&4usVY~2IY-|9<7QnL{PN1drI)UrJ!xpg9lML`=xU{=Zs%_AB^P~bnp6l%Yc>H&rIwvF09-4xGQXAg@E%+l?CC_kR<)&@h zu4z~^GcZiv4rF;WAAFF=q4;D@FC!y^R>qajY|RG`UwQpCiDRKgm(uh(+zbtz7B4GZ z*I(~mc>8VL+p^vKP8^EQW2VgEWoR&z`no%B`|kxniM6%AzvWgLNW@6rojwC7_3^Y~ z%ZI0@r)P`n#mrcjo0}^uFDEBAbuJe}!^xjtUS7U9(>T2^c=c7UUbn;D@eB=I*(OrG zVXLQRNlQ!5?U%Q=>oshXSiDuA!J+ktL~)OyP4%}oFTcLN{@=6LEjhos`uEq9dq1%= z1n63}2yENFJ>FcZckh<{`~N2u7GBf_nHL=sqx0?i_xLGunHd`FSKiHAzR8$@!JrZt z2GjCT-y zIlD?;Ug~{VP+^?jY|qf3b;`0G$oc%}Xt#M*{{4M_3kwPg0?ITP7}TC0Zs%uDKR@qp zVD{E1*R^4@wX6kz5ncT6&(D=+h4zb^X}ch-#xkWPMl#uR1t?_PF~)=-9=AN z?N3Zhd|1eF(0?xjL&z1+^WNOt-2K6O6qA}e^A{sS z$nEp{((iIJ6j;5erFNJye6W67W<7N-BQU-EzWQ6lfuSMm_to!VVPWsu1f0%UPMgKS z(9kLIb)(ZM9$!{9FiQ5j4jHj!g%Q~loCIE5e*3PB2BK0uAij8+kif=W_fG}P8tO`&P5(M(=Vn>a}uW|Fxx_qebT9}!X1vG)7< zFYmeVKbzk@{O^&Gkvo4SWlG%u0GvRKvDTV|Nw|`vCaF2mtnNg&T6ZRj$1>TLv_$|o z{unE(L{q|@&s6RF@j=2SifU_XYm_A4_nqIoNl_F<{iXV~`HXFAYil$e&%My`(H;G7 zWHW{MiZwOG#%(JjpWZh-vG(>0-mJa5Hay3iGxtw^769OX#vFgJ2g;(|QS;oLZ?9i{ z|I(Vh+XsSl&xBi1+_~lcz(QnctgWtW{_wt-Om$@!;-TpD^vfBqzt1n*y2)>R zVr$3Ex7M`W_TZLBu6(s8d13pL?WZ1F!c7PO_!H(ccH?WtjcX&BOy*rHYt$;6@Dq;H z>V)IZe_i=)divV&nbFI?%@4Xd3zi=>}JDU4I(vatQPAZiOOS}&N_^)y#$(L^v zHu2+xz{n<)FwK+A@0GGK0Km@}&+}aCTgR7ml3vnHCX?<$SI&`&xKV?EdO z1id=n$di&uPpM5QrIeB+HKZDBiF*Wq<4UjBYb42&CvQGeQ&NMzvTHimMAg#LvQCnG z-*-yeDggX{&1Y;&OUpX*V#iDSKDcN9zU)X4JN@F5!^Ul^B73(TIy$#@ELpKQ8Vo<0 zoeT>hr(OW~1CAV67h!g`r(XokexAxyv4@UmZ!O z2OAED>GN|66NuBtCmd-&IYr&f}2-%=Gx6yH%EdX*q+t*U(DD^V~o*Q zpX^=F+_fb8O8(vz%g(&4_vLRYmp3i>Z21*c>B)i2?uQ4vyu1NFFW=_5UumHz6 zQ<5|hj3y`gMr|cY)>>nXu|W{*G1gkM$!z-R$689M(DS_b;r@wBt1FhQ9@=wwQFVR% zU`>5|yqGWc@7S^9Ye_~Db6)^h*!-;qN1k--l9(hZX_Mhk3jhHB3iG?6>-)ab-QDeI ur&QDkvbUuHa`Hh#L%~=17L{&0000 Date: Sun, 3 Dec 2023 18:29:22 +0100 Subject: [PATCH 03/31] Hide midi ports toggle that does not work on mod-app yet Signed-off-by: falkTX --- html/index.html | 2 +- html/js/desktop.js | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/html/index.html b/html/index.html index d74b40e8..cc528912 100644 --- a/html/index.html +++ b/html/index.html @@ -162,7 +162,7 @@ $('#mod-bypassRight').hide() } - if ({{using_mod}}) { + if ({{using_mod}} && !{{using_app}}) { desktop.authenticateDevice(function (ok) { if (ok) { console.log("MOD authentication succeeded") diff --git a/html/js/desktop.js b/html/js/desktop.js index df1fd67e..1f085896 100644 --- a/html/js/desktop.js +++ b/html/js/desktop.js @@ -721,6 +721,7 @@ function Desktop(elements) { $('#mod-devices').hide() $('#mod-status').hide() $('#mod-ram').hide() + $('#mod-show-midi-port').hide() $('#pedalboards-library').find('a').hide() $('#pedal-presets-window').find('.js-assign-all').hide() } From 7e4bc4ebe25b03c95c24297b659750c0406b40bf Mon Sep 17 00:00:00 2001 From: falkTX Date: Sun, 3 Dec 2023 23:51:18 +0100 Subject: [PATCH 04/31] Dynamic handling of mod-external client Signed-off-by: falkTX --- mod/host.py | 35 ++++++++++++++++++++++++----------- utils/utils_jack.cpp | 6 +++--- 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/mod/host.py b/mod/host.py index 5965429e..6094f00d 100644 --- a/mod/host.py +++ b/mod/host.py @@ -402,8 +402,8 @@ def __init__(self, hmi, prefs, msg_callback): # clients at the end of the chain, all managed by mod-host self.jack_hw_capture_prefix = "mod-host:out" if self.descriptor.get('has_noisegate', False) else "system:capture_" - # used for network-manager - self.jack_slave_prefix = "mod-slave" + # used for external connections + self.jack_external_prefix = "mod-external" # used for usb gadget, MUST have "c" or "p" after this prefix self.jack_usbgadget_prefix = "mod-usbgadget_" @@ -551,8 +551,8 @@ def jack_port_appeared(self, name, isOutput): name = charPtrToString(name) isOutput = bool(isOutput) - if name.startswith(self.jack_slave_prefix+":"): - name = name.replace(self.jack_slave_prefix+":","") + if name.startswith(self.jack_external_prefix+":"): + name = name.replace(self.jack_external_prefix+":","") if name.startswith("midi_"): ptype = "midi" elif name.startswith(CV_PREFIX): @@ -563,6 +563,12 @@ def jack_port_appeared(self, name, isOutput): index = 100 + int(name.rsplit("_",1)[-1]) title = name.title().replace(" ","_") self.msg_callback("add_hw_port /graph/%s %s %i %s %i" % (name, ptype, int(isOutput), title, index)) + + if ptype == "audio": + if isOutput: + self.audioportsOut.append(name) + else: + self.audioportsIn.append(name) return if name.startswith(self.jack_usbgadget_prefix): @@ -643,6 +649,13 @@ def jack_port_deleted(self, name): self.msg_callback("remove_hw_port /graph/%s" % (name.split(":",1)[-1])) + if name.startswith(self.jack_external_prefix+":"): + name = name.replace(self.jack_external_prefix+":","") + if name in self.audioportsIn: + self.audioportsIn.remove(name) + if name in self.audioportsOut: + self.audioportsOut.remove(name) + def true_bypass_changed(self, left, right): self.msg_callback("truebypass %i %i" % (left, right)) @@ -2086,7 +2099,7 @@ def report_current_state(self, websocket): ports = get_jack_hardware_ports(False, False) for i in range(len(ports)): name = ports[i] - if name not in midiports and not name.startswith("%s:midi_" % self.jack_slave_prefix): + if name not in midiports and not name.startswith("%s:midi_" % self.jack_external_prefix): continue alias = get_jack_port_alias(name) @@ -2109,7 +2122,7 @@ def report_current_state(self, websocket): ports = get_jack_hardware_ports(False, True) for i in range(len(ports)): name = ports[i] - if name not in midiports and not name.startswith("%s:midi_" % self.jack_slave_prefix): + if name not in midiports and not name.startswith("%s:midi_" % self.jack_external_prefix): continue alias = get_jack_port_alias(name) if alias: @@ -3339,11 +3352,11 @@ def _fix_host_connection_port(self, port): if num in monitorportnums: return "mod-monitor:in_" + num - if data[2].startswith(("audio_from_slave_", - "audio_to_slave_", - "midi_from_slave_", - "midi_to_slave_")): - return "%s:%s" % (self.jack_slave_prefix, data[2]) + if data[2].startswith(("audio_from_external_", + "audio_to_external_", + "midi_from_external_", + "midi_to_external_")): + return "%s:%s" % (self.jack_external_prefix, data[2]) if data[2].startswith("USB_Audio_Capture_"): return "%s:%s" % (self.jack_usbgadget_prefix+"c", data[2]) diff --git a/utils/utils_jack.cpp b/utils/utils_jack.cpp index e9494ad6..0f6fb5c6 100644 --- a/utils/utils_jack.cpp +++ b/utils/utils_jack.cpp @@ -24,8 +24,8 @@ #define ALSA_CONTROL_SPDIF_ENABLE "SPDIF Enable" #define ALSA_CONTROL_MASTER_VOLUME "DAC" -#define JACK_SLAVE_PREFIX "mod-slave" -#define JACK_SLAVE_PREFIX_LEN 9 +#define JACK_EXTERNAL_PREFIX "mod-external" +#define JACK_EXTERNAL_PREFIX_LEN 9 // -------------------------------------------------------------------------------------------------------- @@ -101,7 +101,7 @@ static void JackPortRegistration(jack_port_id_t port_id, int reg, void*) if (const char* const port_name = jack_port_name(port)) { if (strncmp(port_name, "system:midi_", 12) != 0 && - strncmp(port_name, JACK_SLAVE_PREFIX ":", JACK_SLAVE_PREFIX_LEN + 1) != 0 && + strncmp(port_name, JACK_EXTERNAL_PREFIX ":", JACK_EXTERNAL_PREFIX_LEN + 1) != 0 && strncmp(port_name, "nooice", 5) != 0) return; From 1d262716af75186b79a8aeeb251d50cf89185534 Mon Sep 17 00:00:00 2001 From: falkTX Date: Sun, 3 Dec 2023 23:59:28 +0100 Subject: [PATCH 05/31] Fix typo Signed-off-by: falkTX --- utils/utils_jack.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/utils_jack.cpp b/utils/utils_jack.cpp index 0f6fb5c6..fab4a924 100644 --- a/utils/utils_jack.cpp +++ b/utils/utils_jack.cpp @@ -25,7 +25,7 @@ #define ALSA_CONTROL_MASTER_VOLUME "DAC" #define JACK_EXTERNAL_PREFIX "mod-external" -#define JACK_EXTERNAL_PREFIX_LEN 9 +#define JACK_EXTERNAL_PREFIX_LEN 12 // -------------------------------------------------------------------------------------------------------- From 1d209802c437e7fbcee399b08900224d0aadf999 Mon Sep 17 00:00:00 2001 From: falkTX Date: Fri, 8 Dec 2023 14:38:19 +0100 Subject: [PATCH 06/31] Add -lm to lilv libs Signed-off-by: falkTX --- utils/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/Makefile b/utils/Makefile index 27c9ee9b..e5bc8d46 100644 --- a/utils/Makefile +++ b/utils/Makefile @@ -58,7 +58,7 @@ JACK_LIBS = $(shell pkg-config --libs jack) endif LILV_CFLAGS = $(shell pkg-config --cflags lilv-0) -Wno-parentheses -LILV_LIBS = $(shell pkg-config --libs lilv-0) +LILV_LIBS = $(shell pkg-config --libs lilv-0) -lm all: build From a4418dc5a3e73b616bddd361be72b038d25f234b Mon Sep 17 00:00:00 2001 From: falkTX Date: Fri, 22 Dec 2023 16:44:09 +0100 Subject: [PATCH 07/31] Always save snapshots, regen screenshot as needed, save indicator Signed-off-by: falkTX --- html/css/pedals.css | 4 ++++ html/js/desktop.js | 33 ++++++++++++++++++++------------- html/js/host.js | 2 +- html/js/snapshot.js | 2 +- mod/host.py | 4 ++++ mod/session.py | 14 +++++++++++++- mod/webserver.py | 4 ++++ 7 files changed, 47 insertions(+), 16 deletions(-) diff --git a/html/css/pedals.css b/html/css/pedals.css index 5dc7664a..f7ed24eb 100644 --- a/html/css/pedals.css +++ b/html/css/pedals.css @@ -1032,3 +1032,7 @@ body > .mod-settings.mod-window-visible { .plugin-description p::selection { background: #4f2157 } + +#pedalboard-info .js-save.unmodified-changes { + background-color: #883996; +} diff --git a/html/js/desktop.js b/html/js/desktop.js index 1f085896..1709c462 100644 --- a/html/js/desktop.js +++ b/html/js/desktop.js @@ -140,7 +140,7 @@ function Desktop(elements) { data: JSON.stringify(addressing), success: function (resp) { if (resp) { - self.pedalboardModified = true + self.setPedalboardAsModified(true) callback(true) } else { new Bug("Couldn't address parameter, not allowed") @@ -467,6 +467,15 @@ function Desktop(elements) { }) } + this.setPedalboardAsModified = function (modified) { + this.pedalboardModified = modified + if (modified) { + elements.saveButton.addClass('unmodified-changes') + } else { + elements.saveButton.removeClass('unmodified-changes') + } + } + elements.devicesIcon.statusTooltip() this.ccDeviceManager = new ControlChainDeviceManager({ devicesIcon: elements.devicesIcon, @@ -540,7 +549,7 @@ function Desktop(elements) { var source = syncMode === "link" ? "Ableton Link" : "MIDI" new Notification('info', 'BPM addressing removed, incompatible with ' + source + ' sync mode', 8000) } - self.pedalboardModified = true + self.setPedalboardAsModified(true) }, setSyncMode: function(syncMode, callback) { $.ajax({ @@ -583,7 +592,7 @@ function Desktop(elements) { var source = syncMode === "link" ? "Ableton Link" : "MIDI" new Notification('info', 'BPM addressing removed, incompatible with ' + source + ' sync mode', 8000) } - self.pedalboardModified = true + self.setPedalboardAsModified(true) callback(true) } else { new Bug("Couldn't address parameter") @@ -855,7 +864,7 @@ function Desktop(elements) { transfer.reportFinished = function () { self.pedalboardEmpty = false - self.pedalboardModified = true + self.setPedalboardAsModified(true) } transfer.reportError = function (error) { @@ -987,7 +996,6 @@ function Desktop(elements) { url: '/snapshot/save', method: 'POST', success: function () { - self.pedalboardModified = true new Notification('info', 'Pedalboard snapshot saved', 2000) }, error: function () { @@ -1015,7 +1023,6 @@ function Desktop(elements) { } self.pedalboardPresetId = resp.id self.pedalboardPresetName = resp.title - self.pedalboardModified = true self.titleBox.text((self.title || 'Untitled') + " - " + resp.title) new Notification('info', 'Pedalboard snapshot saved', 2000) }, @@ -1456,13 +1463,13 @@ Desktop.prototype.makePedalboard = function (el, effectBox) { self.title = '' self.pedalboardBundle = null self.pedalboardEmpty = true - self.pedalboardModified = false self.pedalboardPresetId = 0 self.pedalboardPresetName = '' self.pedalboardDemoPluginsNotified = false self.titleBox.text('Untitled') self.titleBox.addClass("blend") self.transportControls.resetControlsEnabled() + self.setPedalboardAsModified(false) callback(true) }, @@ -1488,7 +1495,7 @@ Desktop.prototype.makePedalboard = function (el, effectBox) { }, pluginParameterChange: function (port, value) { - self.pedalboardModified = true + self.setPedalboardAsModified(true) ws.send(sprintf("param_set %s %f", port, value)) }, @@ -1497,12 +1504,12 @@ Desktop.prototype.makePedalboard = function (el, effectBox) { }, pluginPatchSet: function (instance, uri, valuetype, value) { - self.pedalboardModified = true + self.setPedalboardAsModified(true) ws.send(sprintf("patch_set %s %s %s %s", instance, uri, valuetype, value)) }, pluginMove: function (instance, x, y) { - self.pedalboardModified = true + self.setPedalboardAsModified(true) ws.send(sprintf("plugin_pos %s %f %f", instance, x, y)) }, @@ -1582,7 +1589,7 @@ Desktop.prototype.makePedalboard = function (el, effectBox) { // Bind events el.bind('modified', function () { self.pedalboardEmpty = false - self.pedalboardModified = true + self.setPedalboardAsModified(true) }) /* el.bind('dragStart', function () { @@ -1837,8 +1844,8 @@ Desktop.prototype.loadPedalboard = function (bundlepath, callback) { self.title = resp.name self.pedalboardBundle = bundlepath self.pedalboardEmpty = false - self.pedalboardModified = false self.pedalboardDemoPluginsNotified = false + self.setPedalboardAsModified(false) self.titleBox.text(resp.name); self.titleBox.removeClass("blend"); @@ -1876,7 +1883,7 @@ Desktop.prototype.saveCurrentPedalboard = function (asNew, callback) { self.title = title self.pedalboardBundle = errorOrPath self.pedalboardEmpty = false - self.pedalboardModified = false + self.setPedalboardAsModified(false) self.titleBox.text(title + " - " + self.pedalboardPresetName) if (self.previousPedalboardList != null) { diff --git a/html/js/host.js b/html/js/host.js index fc8637b0..66ebd76b 100644 --- a/html/js/host.js +++ b/html/js/host.js @@ -506,9 +506,9 @@ $('document').ready(function() { success: function (resp) { desktop.pedalboard.pedalboard('scheduleAdapt', true) desktop.pedalboardEmpty = empty && !modified - desktop.pedalboardModified = modified desktop.pedalboardPresetId = snapshotId desktop.pedalboardPresetName = resp.name + desktop.setPedalboardAsModified(modified) if (resp.ok) { desktop.titleBox.text((desktop.title || 'Untitled') + " - " + resp.name) diff --git a/html/js/snapshot.js b/html/js/snapshot.js index 63b21ab3..27a94905 100644 --- a/html/js/snapshot.js +++ b/html/js/snapshot.js @@ -102,7 +102,7 @@ function SnapshotsManager(options) { options.pedalPresetsWindow.find('.js-assign-all').addClass('disabled') } - // Replace options value and text so we can a sequential list 0, 1, 2, etc. + // Replace options value and text so we get a sequential list 1, 2, 3, etc. var i = 0 options.pedalPresetsList.children().each(function(option) { var optionHtml = $(this).html() diff --git a/mod/host.py b/mod/host.py index 6094f00d..ed70a140 100644 --- a/mod/host.py +++ b/mod/host.py @@ -3270,6 +3270,10 @@ def snapshot_load(self, idx, from_hmi, abort_catcher, callback): # callback must be last action callback(True) + def save_snapshots_to_disk(self): + if self.pedalboard_path: + self.save_state_snapshots(self.pedalboard_path) + @gen.coroutine def page_load(self, idx, abort_catcher, callback): if not self.addressings.addressing_pages: diff --git a/mod/session.py b/mod/session.py index bcd0a25a..4e7da8f0 100644 --- a/mod/session.py +++ b/mod/session.py @@ -69,6 +69,7 @@ def __init__(self): self.recordhandle = None self.external_ui_timer = None + self.screenshot_needed = False self.screenshot_generator = ScreenshotGenerator() self.websockets = [] @@ -155,10 +156,12 @@ def hmi_reinit_cb(self): # Add a new plugin, starts enabled (ie, not bypassed) def web_add(self, instance, uri, x, y, callback): + self.screenshot_needed = True self.host.add_plugin(instance, uri, x, y, callback) # Remove a plugin def web_remove(self, instance, callback): + self.screenshot_needed = True self.host.remove_plugin(instance, callback) # Address a plugin parameter @@ -181,10 +184,12 @@ def web_set_sync_mode(self, mode, callback): # Connect 2 ports def web_connect(self, port_from, port_to, callback): + self.screenshot_needed = True self.host.connect(port_from, port_to, callback) # Disconnect 2 ports def web_disconnect(self, port_from, port_to, callback): + self.screenshot_needed = True self.host.disconnect(port_from, port_to, callback) # Save the current pedalboard @@ -196,7 +201,10 @@ def web_save_pedalboard(self, title, asNew, callback): if self.hmi.initialized and self.host.descriptor.get('hmi_set_pb_name', False): self.hmi_set_pb_name(newTitle or title) - self.screenshot_generator.schedule_screenshot(bundlepath) + if bundlepath and self.screenshot_needed: + self.screenshot_needed = False + self.screenshot_generator.schedule_screenshot(bundlepath) + return bundlepath, newTitle # Get list of Hardware MIDI devices @@ -334,11 +342,13 @@ def ws_patch_set(self, instance, uri, valuetype, valuedata, ws): # Set a plugin block position within the canvas def ws_plugin_position(self, instance, x, y, ws): + self.screenshot_needed = True self.host.set_position(instance, x, y) self.msg_callback_broadcast("plugin_pos %s %d %d" % (instance, x, y), ws) # set the size of the pedalboard (in 1:1 view, aka "full zoom") def ws_pedalboard_size(self, width, height): + self.screenshot_needed = True self.host.set_pedalboard_size(width, height) def ws_show_external_ui(self, instance): @@ -412,6 +422,7 @@ def msg_callback_broadcast(self, msg, ws2): ws.write_message(msg) def load_pedalboard(self, bundlepath, isDefault): + self.screenshot_needed = False self.host.send_notmodified("feature_enable processing 0") title = self.host.load(bundlepath, isDefault) self.host.send_notmodified("feature_enable processing 1") @@ -429,6 +440,7 @@ def load_pedalboard(self, bundlepath, isDefault): def reset(self, callback): logging.debug("SESSION RESET") + self.screenshot_needed = False self.host.send_notmodified("feature_enable processing 0") def host_callback(resp): diff --git a/mod/webserver.py b/mod/webserver.py index 864b1581..d9d7d874 100644 --- a/mod/webserver.py +++ b/mod/webserver.py @@ -1599,6 +1599,7 @@ def post(self, mode): class SnapshotSave(JsonRequestHandler): def post(self): ok = SESSION.host.snapshot_save() + SESSION.host.save_snapshots_to_disk() self.write(ok) class SnapshotSaveAs(JsonRequestHandler): @@ -1611,6 +1612,7 @@ def get(self): yield gen.Task(SESSION.host.hmi_report_ss_name_if_current, idx) + SESSION.host.save_snapshots_to_disk() self.write({ 'ok': idx is not None, 'id': idx, @@ -1630,6 +1632,7 @@ def get(self): yield gen.Task(SESSION.host.hmi_report_ss_name_if_current, idx) + SESSION.host.save_snapshots_to_disk() self.write({ 'ok': ok, 'title': title, @@ -1639,6 +1642,7 @@ class SnapshotRemove(JsonRequestHandler): def get(self): idx = int(self.get_argument('id')) ok = SESSION.host.snapshot_remove(idx) + SESSION.host.save_snapshots_to_disk() self.write(ok) class SnapshotList(JsonRequestHandler): From c81f520bd72f4dc59475b0b97cbe3c3b2ec9b809 Mon Sep 17 00:00:00 2001 From: falkTX Date: Sat, 30 Dec 2023 20:11:22 +0100 Subject: [PATCH 08/31] Make "only show plugins with modgui" a runtime option Signed-off-by: falkTX --- utils/utils_lilv.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/utils/utils_lilv.cpp b/utils/utils_lilv.cpp index cd7e96bc..d63ec23d 100644 --- a/utils/utils_lilv.cpp +++ b/utils/utils_lilv.cpp @@ -186,6 +186,7 @@ static size_t HOMElen = strlen(HOME); // configuration static const bool kAllowRegularCV = getenv("MOD_UI_ALLOW_REGULAR_CV") != nullptr; +static const bool kOnlyShowPluginsWithMODGUI = getenv("MOD_UI_ONLY_SHOW_PLUGINS_WITH_MODGUI") != nullptr; #define PluginInfo_Init { \ false, \ @@ -4289,10 +4290,8 @@ const PluginInfo_Mini* const* get_all_plugins(void) if (const PluginInfo_Mini* const miniInfo = PLUGNFO_Mini[uri]) { -#if SHOW_ONLY_PLUGINS_WITH_MODGUI - if (miniInfo->gui.resourcesDirectory == nc) + if (kOnlyShowPluginsWithMODGUI && miniInfo->gui.resourcesDirectory == nc) continue; -#endif _get_plugs_mini_ret[curIndex++] = PLUGNFO_Mini[uri]; } } From c5e4f5d10cfd5d696b99ee338598948527f7f11b Mon Sep 17 00:00:00 2001 From: falkTX Date: Wed, 3 Jan 2024 10:33:09 +0100 Subject: [PATCH 09/31] Swap old pycrypto for pycryptodomex in requirements.txt Signed-off-by: falkTX --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 53b9822e..6208e9e0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ Pillow>=8.2.0 pyserial==3.0 tornado==4.3 -git+https://github.com/dlitz/pycrypto.git@master#egg=pycrypto +pycryptodomex==3.18.0 aggdraw==1.3.11 # for making old tornado python3.10 compatible: From 757efcaadc56117628dd48cb0ffc206f046d7e22 Mon Sep 17 00:00:00 2001 From: Luis Fagundes Date: Wed, 3 Jan 2024 09:30:35 -0300 Subject: [PATCH 10/31] Track user events using Matomo (WIP) This is intended for the desktop app. We still need to disable it by default, and add acceptance of privacy policy. --- html/index.html | 11 +++++++++++ html/js/desktop.js | 2 ++ html/js/pedalboard.js | 3 +++ 3 files changed, 16 insertions(+) diff --git a/html/index.html b/html/index.html index cc528912..97fc4abf 100644 --- a/html/index.html +++ b/html/index.html @@ -282,6 +282,17 @@ + + + + diff --git a/html/js/desktop.js b/html/js/desktop.js index 1709c462..8bf51b73 100644 --- a/html/js/desktop.js +++ b/html/js/desktop.js @@ -931,6 +931,7 @@ function Desktop(elements) { self.waitForScreenshot(false, result.bundlepath, function(){}) // all set callback(true, result.bundlepath, result.title) + _paq.push(['trackEvent', 'pedalboard', 'pedalboard-save']) } else { callback(false, "Failed to save") } @@ -1850,6 +1851,7 @@ Desktop.prototype.loadPedalboard = function (bundlepath, callback) { self.titleBox.removeClass("blend"); callback(true) + _paq.push(['trackEvent', 'pedalboard', 'pedalboard-load']); }, error: function () { new Bug("Couldn't load pedalboard") diff --git a/html/js/pedalboard.js b/html/js/pedalboard.js index e44da9a4..ee2ccf30 100644 --- a/html/js/pedalboard.js +++ b/html/js/pedalboard.js @@ -384,6 +384,9 @@ JqueryClass('pedalboard', { width: ui.helper.children().width(), height: ui.helper.children().height() }) + var uri = unescape(ui.draggable.attr('mod-uri')); + var count = self.find('.mod-actions').length; + _paq.push(['trackEvent', 'plugin', 'plugin-add', uri, count]); } }) From fc3ca57058c7a029f319c263e7343bcbaef8d86c Mon Sep 17 00:00:00 2001 From: Luis Fagundes Date: Tue, 16 Jan 2024 07:39:47 -0300 Subject: [PATCH 11/31] Support browsers with privacy shield while tracking with Matomo --- html/index.html | 3 +++ 1 file changed, 3 insertions(+) diff --git a/html/index.html b/html/index.html index 97fc4abf..c98148df 100644 --- a/html/index.html +++ b/html/index.html @@ -282,6 +282,9 @@ + + + diff --git a/html/terms.html b/html/terms.html new file mode 100644 index 00000000..46e01ec0 --- /dev/null +++ b/html/terms.html @@ -0,0 +1,5 @@ + + + Here goes terms, and a button to accept + + From 828765bae2989025f248adb2d9292d3c0d28f992 Mon Sep 17 00:00:00 2001 From: falkTX Date: Thu, 18 Jan 2024 17:04:25 +0100 Subject: [PATCH 13/31] Setup tos/pp dialog for online usage (WIP) Signed-off-by: falkTX --- html/css/pedals.css | 16 ++++++ html/index.html | 116 ++++++++++++++++++++++++++++++++------------ html/js/desktop.js | 35 ++++++++++++- html/terms.html | 5 -- mod/webserver.py | 2 +- 5 files changed, 137 insertions(+), 37 deletions(-) delete mode 100644 html/terms.html diff --git a/html/css/pedals.css b/html/css/pedals.css index f7ed24eb..aa422178 100644 --- a/html/css/pedals.css +++ b/html/css/pedals.css @@ -1036,3 +1036,19 @@ body > .mod-settings.mod-window-visible { #pedalboard-info .js-save.unmodified-changes { background-color: #883996; } + +#mod-cloud-terms { + z-index: 100000; +} +#mod-cloud-terms .mod-box { + width: 500px; + left: 0; + right: 0; + margin: -150px auto; +} +#mod-cloud-terms .actions { + text-align: center; +} +#mod-cloud-terms .btn { + margin: 0px 4px; +} diff --git a/html/index.html b/html/index.html index 8104606a..92df3466 100644 --- a/html/index.html +++ b/html/index.html @@ -84,7 +84,9 @@ console.log = function() {} } - var TERMS_ACCEPTED = true; + var CLOUD_TERMS_ACCEPTED = PREFERENCES['cloud-terms-accepted'] === "true"; + var USING_MOD_APP = {{using_app}}; + var USING_MOD_DEVICE = {{using_mod}} && !{{using_app}}; /* var INFO = { @@ -98,7 +100,7 @@ // for mod-app control var desktop = null -var startApp = function() { +$('document').ready(function() { desktop = new Desktop({ titleBox: $('#pedalboard-info h1'), pedalboard: $('#pedalboard-dashboard'), @@ -164,16 +166,10 @@ $('#mod-bypassRight').hide() } - if ({{using_mod}} && !{{using_app}}) { - desktop.authenticateDevice(function (ok) { - if (ok) { - console.log("MOD authentication succeeded") - desktop.resetPedalboardStats(); - } else { - console.log("MOD authentication failed") - desktop.upgradeWindow.upgradeWindow('setErrored') - } - }) + if (USING_MOD_DEVICE) { + if (CLOUD_TERMS_ACCEPTED) { + desktop.setupDeviceAuthentication() + } } else { desktop.upgradeWindow.upgradeWindow('setErrored') @@ -221,6 +217,36 @@ // end code for ajax loading bar + // start of terms handling + + var terms = $('#mod-cloud-terms') + if (PREFERENCES['cloud-terms-accepted'] !== undefined && (USING_MOD_DEVICE || PREFERENCES['cloud-terms-accepted'] === "true")) { + if (CLOUD_TERMS_ACCEPTED) { + desktop.setupMatomo() + terms.hide() + $('#pedalboard-info').find('.js-cloud').show() + } + terms.hide() + } else { + terms.find('.js-accept').click(function () { + CLOUD_TERMS_ACCEPTED = true + desktop.saveConfigValue("cloud-terms-accepted", true) + desktop.setupMatomo() + if (USING_MOD_DEVICE) { + desktop.setupDeviceAuthentication() + $('#pedalboard-info').find('.js-cloud').show() + } + terms.hide() + }) + terms.find('.js-reject').click(function () { + desktop.saveConfigValue("cloud-terms-accepted", false) + terms.hide() + }) + terms.show() + } + + // end of terms handling + /* $.ajax({ url: '/system/info', @@ -253,12 +279,7 @@ } })(); */ -}; -if (TERMS_ACCEPTED) { - $('document').ready(startApp); -} else { - document.location.href = '/terms.html'; -} +}) @@ -292,17 +313,6 @@ - - - - @@ -354,6 +364,52 @@
+{% if using_app == 'true' or using_mod == 'true' %} + +
+
+ {% if using_app == 'true' %} +

Terms of Service for MOD Desktop App

+
+

+ (NOTE: THIS AN EXAMPLE TEXT YET TO BE REVIEWED)
+ The use of this application...
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
+ Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
+

+

+ Please review our Terms of Service and Privacy Policy before continuing.
+

+
+ {% else %} +

Terms of Service for MOD online components

+ × +
+
+

+ (NOTE: THIS AN EXAMPLE TEXT YET TO BE REVIEWED)
+ Your MOD unit can receive firmware updates over the internet, and also download new plugins and pedalboards.
+ The use of these online services is under Terms of Service and Privacy Policy in order to comply with different regulatory laws across the globe.
+

+

+ Please review our Terms of Service and Privacy Policy before continuing.
+ While you can freely use your MOD unit without accepting these terms, you must agree to them in order to enable or perform any online interaction. +

+
+ {% end %} +
+ {% if using_app == 'true' %} + + {% else %} + + + {% end %} +
+
+
+ +{% end %} +
@@ -530,7 +586,7 @@

{{fulltitle}}

- +
diff --git a/html/js/desktop.js b/html/js/desktop.js index 8bf51b73..631a98bd 100644 --- a/html/js/desktop.js +++ b/html/js/desktop.js @@ -102,6 +102,9 @@ function Desktop(elements) { this.pedalboardStats = {}; this.resetPedalboardStats = function() { this.pedalboardStatsSuccess = false; + if (! CLOUD_TERMS_ACCEPTED) { + return + } $.ajax({ url: SITEURL + '/pedalboards/stats', type: 'GET', @@ -330,7 +333,7 @@ function Desktop(elements) { callback(pedals, '') } - else + else if (CLOUD_TERMS_ACCEPTED) { // NOTE: this part is never called. pedalboard search is always local $.ajax({ @@ -710,6 +713,7 @@ function Desktop(elements) { success: function () { if (callback) { callback(true) + PREFERENCES[key] = value } }, error: function () { @@ -735,6 +739,27 @@ function Desktop(elements) { $('#pedal-presets-window').find('.js-assign-all').hide() } + this.setupDeviceAuthentication = function () { + self.authenticateDevice(function (ok) { + if (ok) { + console.log("MOD authentication succeeded") + self.resetPedalboardStats(); + } else { + console.log("MOD authentication failed") + self.upgradeWindow.upgradeWindow('setErrored') + } + }) + } + + this.setupMatomo = function() { + var _mtm = window._mtm = window._mtm || []; + _mtm.push({'mtm.startTime': (new Date().getTime()), 'event': 'mtm.Start'}); + (function() { + var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0]; + g.async=true; g.src='https://cdn.matomo.cloud/modaudio.matomo.cloud/container_DfEOyKDN.js'; s.parentNode.insertBefore(g,s); + })(); + } + this.effectBox = self.makeEffectBox(elements.effectBox, elements.effectBoxTrigger) this.cloudPluginBox = self.makeCloudPluginBox(elements.cloudPluginBox, @@ -831,6 +856,10 @@ function Desktop(elements) { }, this.loadRemotePedalboard = function (pedalboard_id) { + if (! CLOUD_TERMS_ACCEPTED) { + return + } + self.windowManager.closeWindows(null, true) if (self.cloudAccessToken == null) { @@ -1177,6 +1206,10 @@ function Desktop(elements) { }) } + if (! CLOUD_TERMS_ACCEPTED) { + return + } + if (self.cloudAccessToken == null) { self.authenticateDevice(function (ok) { if (ok && self.cloudAccessToken != null) { diff --git a/html/terms.html b/html/terms.html deleted file mode 100644 index 46e01ec0..00000000 --- a/html/terms.html +++ /dev/null @@ -1,5 +0,0 @@ - - - Here goes terms, and a button to accept - - diff --git a/mod/webserver.py b/mod/webserver.py index d9d7d874..ad576a6d 100644 --- a/mod/webserver.py +++ b/mod/webserver.py @@ -1812,7 +1812,7 @@ def index(self): 'titleblend': '' if SESSION.host.pedalboard_name else 'blend', 'dev_api_class': 'dev_api' if DEV_API else '', 'using_app': 'true' if APP else 'false', - 'using_mod': 'true' if DEVICE_KEY or DEV_HOST else 'false', + 'using_mod': 'true' if DEVICE_KEY and hwdesc.get('platform', None) is not None else 'false', 'user_name': mod_squeeze(user_id.get("name", "")), 'user_email': mod_squeeze(user_id.get("email", "")), 'favorites': json.dumps(gState.favorites), From 981276e3164366a9d9353a9f5d584b4121bdac58 Mon Sep 17 00:00:00 2001 From: falkTX Date: Fri, 19 Jan 2024 16:51:59 +0100 Subject: [PATCH 14/31] Improved terms text for mod-app Signed-off-by: falkTX --- html/index.html | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/html/index.html b/html/index.html index 92df3466..4111a1cb 100644 --- a/html/index.html +++ b/html/index.html @@ -369,16 +369,19 @@
{% if using_app == 'true' %} -

Terms of Service for MOD Desktop App

+

Welcome to the MOD Desktop App!

- (NOTE: THIS AN EXAMPLE TEXT YET TO BE REVIEWED)
- The use of this application...
- Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
- Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
-

-

- Please review our Terms of Service and Privacy Policy before continuing.
+ We're thrilled to have you on board and can't wait for you to explore all the amazing features we've built for you. + Before you dive in, there's just a little bit of housekeeping to take care of.
+ By using the MOD Desktop App, you're agreeing to our + Terms of Service + and + Privacy Policy. + It outlines how we work together, use your data, and protect your privacy – ensuring a safe and enjoyable experience for everyone.
+ So, take a quick moment to review these documents. + Once you're all set, the world of MOD is yours to explore. + Happy modding!

{% else %} @@ -392,7 +395,10 @@

Terms of Service for MOD online components

The use of these online services is under Terms of Service and Privacy Policy in order to comply with different regulatory laws across the globe.

- Please review our Terms of Service and Privacy Policy before continuing.
+ Please review our + Terms of Service + and + Privacy Policy before continuing.
While you can freely use your MOD unit without accepting these terms, you must agree to them in order to enable or perform any online interaction.

From a0c17705d773bcaba35bc58b2ee665e95117b042 Mon Sep 17 00:00:00 2001 From: falkTX Date: Fri, 19 Jan 2024 17:25:17 +0100 Subject: [PATCH 15/31] mod-desktop-app rename Signed-off-by: falkTX --- utils/utils_jack.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/utils_jack.cpp b/utils/utils_jack.cpp index fab4a924..1fc8b2be 100644 --- a/utils/utils_jack.cpp +++ b/utils/utils_jack.cpp @@ -201,7 +201,7 @@ bool init_jack(void) #ifdef MODAPP const jack_options_t options = static_cast(JackNoStartServer|JackUseExactName|JackServerName); - jack_client_t* const client = jack_client_open("mod-ui", options, nullptr, "mod-app"); + jack_client_t* const client = jack_client_open("mod-ui", options, nullptr, "mod-desktop-app"); #else const jack_options_t options = static_cast(JackNoStartServer|JackUseExactName); jack_client_t* const client = jack_client_open("mod-ui", options, nullptr); From 27508b32444df583fe0e162e987561636ab893e5 Mon Sep 17 00:00:00 2001 From: falkTX Date: Sat, 20 Jan 2024 12:51:14 +0100 Subject: [PATCH 16/31] Fix loading pb with invalid CV addressings from missing plugins Signed-off-by: falkTX --- mod/addressings.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/mod/addressings.py b/mod/addressings.py index 7d09d141..6a25fdfa 100644 --- a/mod/addressings.py +++ b/mod/addressings.py @@ -674,8 +674,12 @@ def registerMappings(self, msg_callback, instances): for actuator_uri, addrs in self.cv_addressings.items(): # pluginData = self._task_get_plugin_data(instance_id) if not self.is_hw_cv_port(actuator_uri): - operational_mode = self._task_get_plugin_cv_port_op_mode(actuator_uri) - msg_callback("add_cv_port %s %s %s" % (actuator_uri, addrs['name'].replace(" ","_"), operational_mode)) + try: + operational_mode = self._task_get_plugin_cv_port_op_mode(actuator_uri) + except KeyError: + pass + else: + msg_callback("add_cv_port %s %s %s" % (actuator_uri, addrs['name'].replace(" ","_"), operational_mode)) # HMI group_mappings = [] #{} if self.addressing_pages else [] From 9e769f8cd88c70ce20f48acb6e9dd74e0b5adf9d Mon Sep 17 00:00:00 2001 From: falkTX Date: Mon, 29 Jan 2024 00:31:03 +0100 Subject: [PATCH 17/31] Fix startup when PEDALBOARD_TMP_DIR exists but FS is read-only Signed-off-by: falkTX --- mod/__init__.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/mod/__init__.py b/mod/__init__.py index bab760f8..3998841e 100644 --- a/mod/__init__.py +++ b/mod/__init__.py @@ -59,9 +59,14 @@ def check_environment(): # create temp dirs if not os.path.exists(DOWNLOAD_TMP_DIR): os.makedirs(DOWNLOAD_TMP_DIR) + if os.path.exists(PEDALBOARD_TMP_DIR): - shutil.rmtree(PEDALBOARD_TMP_DIR) - os.makedirs(PEDALBOARD_TMP_DIR) + try: + shutil.rmtree(PEDALBOARD_TMP_DIR) + except OSError: + pass + else: + os.makedirs(PEDALBOARD_TMP_DIR) # remove temp files for path in (CAPTURE_PATH, PLAYBACK_PATH, UPDATE_CC_FIRMWARE_FILE): @@ -105,7 +110,7 @@ def check_environment(): if os.path.exists(UPDATE_MOD_OS_HERLPER_FILE): os.remove(UPDATE_MOD_OS_HERLPER_FILE) - os.sync() + os_sync() return True From f2b6a7aceb951787146cb12dd4b2bbee268f5e30 Mon Sep 17 00:00:00 2001 From: falkTX Date: Thu, 1 Feb 2024 21:39:59 +0100 Subject: [PATCH 18/31] Final mod-desktop rename Signed-off-by: falkTX --- html/index.html | 4 ++-- utils/Makefile | 4 ++-- utils/utils_jack.cpp | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/html/index.html b/html/index.html index 4111a1cb..9e9cc8a8 100644 --- a/html/index.html +++ b/html/index.html @@ -369,12 +369,12 @@
{% if using_app == 'true' %} -

Welcome to the MOD Desktop App!

+

Welcome to the MOD Desktop!

We're thrilled to have you on board and can't wait for you to explore all the amazing features we've built for you. Before you dive in, there's just a little bit of housekeeping to take care of.
- By using the MOD Desktop App, you're agreeing to our + By using the MOD Desktop, you're agreeing to our Terms of Service and Privacy Policy. diff --git a/utils/Makefile b/utils/Makefile index e5bc8d46..ac16c9d1 100644 --- a/utils/Makefile +++ b/utils/Makefile @@ -33,8 +33,8 @@ LDFLAGS += -Wl,-O1,--no-undefined,--strip-all endif endif -ifeq ($(MODAPP),1) -CXXFLAGS += -DMODAPP +ifeq ($(MOD_DESKTOP),1) +CXXFLAGS += -D_MOD_DESKTOP ifneq ($(MACOS)$(WINDOWS),true) LDFLAGS += -Wl,-rpath,'$$ORIGIN/..' endif diff --git a/utils/utils_jack.cpp b/utils/utils_jack.cpp index 1fc8b2be..62de8b56 100644 --- a/utils/utils_jack.cpp +++ b/utils/utils_jack.cpp @@ -199,9 +199,9 @@ bool init_jack(void) return true; } -#ifdef MODAPP +#ifdef _MOD_DESKTOP const jack_options_t options = static_cast(JackNoStartServer|JackUseExactName|JackServerName); - jack_client_t* const client = jack_client_open("mod-ui", options, nullptr, "mod-desktop-app"); + jack_client_t* const client = jack_client_open("mod-ui", options, nullptr, "mod-desktop"); #else const jack_options_t options = static_cast(JackNoStartServer|JackUseExactName); jack_client_t* const client = jack_client_open("mod-ui", options, nullptr); From 8905ca6e8bc043bdeaaa0ed03b569e1c1ee0157a Mon Sep 17 00:00:00 2001 From: falkTX Date: Fri, 2 Feb 2024 12:01:29 +0100 Subject: [PATCH 19/31] Tweak wording Signed-off-by: falkTX --- html/index.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/html/index.html b/html/index.html index 9e9cc8a8..6cf9a02a 100644 --- a/html/index.html +++ b/html/index.html @@ -369,16 +369,16 @@

{% if using_app == 'true' %} -

Welcome to the MOD Desktop!

+

Welcome to MOD Desktop!

We're thrilled to have you on board and can't wait for you to explore all the amazing features we've built for you. Before you dive in, there's just a little bit of housekeeping to take care of.
- By using the MOD Desktop, you're agreeing to our + By using this application you're agreeing to our Terms of Service and Privacy Policy. - It outlines how we work together, use your data, and protect your privacy – ensuring a safe and enjoyable experience for everyone.
+ They outline how we work together, use your data, and protect your privacy – ensuring a safe and enjoyable experience for everyone.
So, take a quick moment to review these documents. Once you're all set, the world of MOD is yours to explore. Happy modding!
From 99d9aa25083568c56cf584c8ee40d467268296c5 Mon Sep 17 00:00:00 2001 From: falkTX Date: Sat, 3 Feb 2024 21:16:19 +0100 Subject: [PATCH 20/31] Remove unused set_cpu_affinity, cleanup Signed-off-by: falkTX --- modtools/utils.py | 9 --------- utils/utils.h | 3 --- utils/utils_lilv.cpp | 15 ++------------- 3 files changed, 2 insertions(+), 25 deletions(-) diff --git a/modtools/utils.py b/modtools/utils.py index a6d53734..de3ed8d5 100644 --- a/modtools/utils.py +++ b/modtools/utils.py @@ -616,9 +616,6 @@ class JackData(Structure): utils.file_uri_parse.argtypes = (c_char_p,) utils.file_uri_parse.restype = c_char_p -utils.set_cpu_affinity.argtypes = (c_int,) -utils.set_cpu_affinity.restype = None - utils.init_jack.argtypes = None utils.init_jack.restype = c_bool @@ -918,12 +915,6 @@ def get_bundle_dirname(bundleuri): return bundle -# ------------------------------------------------------------------------------------------------------------ -# helper utilities - -def set_cpu_affinity(cpu): - utils.set_cpu_affinity(cpu) - # ------------------------------------------------------------------------------------------------------------ # jack stuff diff --git a/utils/utils.h b/utils/utils.h index 0affa3f0..2f2473d9 100644 --- a/utils/utils.h +++ b/utils/utils.h @@ -427,9 +427,6 @@ MOD_API const char* const* list_plugins_in_bundle(const char* bundle); // Convert a file URI to a local path string. MOD_API const char* file_uri_parse(const char* fileuri); -// helper utilities -MOD_API void set_cpu_affinity(int cpu); - // jack stuff MOD_API bool init_jack(void); MOD_API void close_jack(void); diff --git a/utils/utils_lilv.cpp b/utils/utils_lilv.cpp index d63ec23d..a18af187 100644 --- a/utils/utils_lilv.cpp +++ b/utils/utils_lilv.cpp @@ -242,7 +242,7 @@ inline bool contains(const std::unordered_map& map, const std::s return map.find(value) != map.end(); } -inline bool ends_with(const std::string& value, const std::string ending) +inline bool ends_with(const std::string& value, const std::string& ending) { if (ending.size() > value.size()) return false; @@ -267,7 +267,7 @@ inline std::string sha1(const char* const cstring) uint8_t* const hashenc = sha1_result(&s); for (int i=0; i Date: Tue, 6 Feb 2024 12:43:37 +0100 Subject: [PATCH 21/31] s/MOD_APP/MOD_DESKTOP/ Signed-off-by: falkTX --- html/index.html | 12 ++++++------ mod/profile.py | 2 +- mod/screenshot.py | 4 ++-- mod/settings.py | 2 +- mod/webserver.py | 6 +++--- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/html/index.html b/html/index.html index 6cf9a02a..5c6dd02c 100644 --- a/html/index.html +++ b/html/index.html @@ -85,8 +85,8 @@ } var CLOUD_TERMS_ACCEPTED = PREFERENCES['cloud-terms-accepted'] === "true"; - var USING_MOD_APP = {{using_app}}; - var USING_MOD_DEVICE = {{using_mod}} && !{{using_app}}; + var USING_MOD_DESKTOP = {{using_desktop}}; + var USING_MOD_DEVICE = {{using_mod}} && !{{using_desktop}}; /* var INFO = { @@ -193,7 +193,7 @@ } desktop.pedalboardBox.pedalboardBox('initViewMode', PREFERENCES['pb-view-mode']) - if ({{using_app}}) { + if ({{using_desktop}}) { desktop.setupApp() } else { if (PREFERENCES['dev-mode'] == "on") { @@ -364,11 +364,11 @@

-{% if using_app == 'true' or using_mod == 'true' %} +{% if using_desktop == 'true' or using_mod == 'true' %}
- {% if using_app == 'true' %} + {% if using_desktop == 'true' %}

Welcome to MOD Desktop!

@@ -404,7 +404,7 @@

Terms of Service for MOD online components

{% end %}
- {% if using_app == 'true' %} + {% if using_desktop == 'true' %} {% else %} diff --git a/mod/profile.py b/mod/profile.py index 1649996e..6809f75b 100644 --- a/mod/profile.py +++ b/mod/profile.py @@ -14,7 +14,7 @@ from tornado.ioloop import IOLoop from mod import TextFileFlusher, safe_json_load -from mod.settings import APP, DATA_DIR +from mod.settings import DATA_DIR def index_to_filepath(index): return os.path.join(DATA_DIR, "profile{0}.json".format(index)) diff --git a/mod/screenshot.py b/mod/screenshot.py index de05d917..ba3c2dd5 100644 --- a/mod/screenshot.py +++ b/mod/screenshot.py @@ -8,7 +8,7 @@ import logging from tornado.ioloop import IOLoop -from mod.settings import HTML_DIR, DEV_ENVIRONMENT, DEVICE_KEY, CACHE_DIR, APP +from mod.settings import HTML_DIR, DEV_ENVIRONMENT, DEVICE_KEY, CACHE_DIR, DESKTOP def generate_screenshot(bundle_path, callback): @@ -24,7 +24,7 @@ def generate_screenshot(bundle_path, callback): cwd = os.path.abspath(os.path.join(os.path.dirname(__file__), '../')) # running packaged through cxfreeze - if APP and os.path.isfile(sys.argv[0]): + if DESKTOP and os.path.isfile(sys.argv[0]): cmd = [os.path.join(cwd, 'mod-pedalboard'), 'take_screenshot', bundle_path, HTML_DIR, CACHE_DIR] if sys.platform == 'win32': cmd[0] += ".exe" diff --git a/mod/settings.py b/mod/settings.py index 2109543f..498ffb81 100644 --- a/mod/settings.py +++ b/mod/settings.py @@ -12,7 +12,7 @@ # If on, use dev cloud API environment DEV_API = bool(int(os.environ.get('MOD_DEV_API', False))) -APP = bool(int(os.environ.get('MOD_APP', False))) +DESKTOP = bool(int(os.environ.get('MOD_DESKTOP', False))) LOG = int(os.environ.get('MOD_LOG', 0)) API_KEY = os.environ.pop('MOD_API_KEY', None) diff --git a/mod/webserver.py b/mod/webserver.py index ad576a6d..d84f5d0d 100644 --- a/mod/webserver.py +++ b/mod/webserver.py @@ -28,7 +28,7 @@ haveSignal = False from mod.profile import Profile -from mod.settings import (APP, LOG, DEV_API, +from mod.settings import (DESKTOP, LOG, DEV_API, HTML_DIR, DOWNLOAD_TMP_DIR, DEVICE_KEY, DEVICE_WEBSERVER_PORT, CLOUD_HTTP_ADDRESS, CLOUD_LABS_HTTP_ADDRESS, PLUGINS_HTTP_ADDRESS, PEDALBOARDS_HTTP_ADDRESS, CONTROLCHAIN_HTTP_ADDRESS, @@ -1811,7 +1811,7 @@ def index(self): 'fulltitle': xhtml_escape(fullpbname), 'titleblend': '' if SESSION.host.pedalboard_name else 'blend', 'dev_api_class': 'dev_api' if DEV_API else '', - 'using_app': 'true' if APP else 'false', + 'using_desktop': 'true' if DESKTOP else 'false', 'using_mod': 'true' if DEVICE_KEY and hwdesc.get('platform', None) is not None else 'false', 'user_name': mod_squeeze(user_id.get("name", "")), 'user_email': mod_squeeze(user_id.get("email", "")), @@ -2468,7 +2468,7 @@ def prepare(isModApp = False): signal(SIGUSR2, signal_recv) set_process_name("mod-ui") - application.listen(DEVICE_WEBSERVER_PORT, address=("localhost" if APP else "0.0.0.0")) + application.listen(DEVICE_WEBSERVER_PORT, address=("localhost" if DESKTOP else "0.0.0.0")) def checkhost(): if SESSION.host.readsock is None or SESSION.host.writesock is None: From 214d64d366ec9542ba9a6d61415705754125c2ad Mon Sep 17 00:00:00 2001 From: falkTX Date: Sat, 10 Feb 2024 11:49:36 +0100 Subject: [PATCH 22/31] Use 127.0.0.1 for desktop instead of localhost Signed-off-by: falkTX --- mod/webserver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mod/webserver.py b/mod/webserver.py index d84f5d0d..27cc4928 100644 --- a/mod/webserver.py +++ b/mod/webserver.py @@ -2468,7 +2468,7 @@ def prepare(isModApp = False): signal(SIGUSR2, signal_recv) set_process_name("mod-ui") - application.listen(DEVICE_WEBSERVER_PORT, address=("localhost" if DESKTOP else "0.0.0.0")) + application.listen(DEVICE_WEBSERVER_PORT, address=("127.0.0.1" if DESKTOP else "0.0.0.0")) def checkhost(): if SESSION.host.readsock is None or SESSION.host.writesock is None: From 318a5d31d118b8b2162255fbd2aed83eacc0b106 Mon Sep 17 00:00:00 2001 From: falkTX Date: Mon, 12 Feb 2024 10:59:59 +0100 Subject: [PATCH 23/31] Update terms links for desktop specific usage Signed-off-by: falkTX --- html/index.html | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/html/index.html b/html/index.html index 5c6dd02c..084a443b 100644 --- a/html/index.html +++ b/html/index.html @@ -375,9 +375,9 @@

Welcome to MOD Desktop!

We're thrilled to have you on board and can't wait for you to explore all the amazing features we've built for you. Before you dive in, there's just a little bit of housekeeping to take care of.
By using this application you're agreeing to our - Terms of Service + Terms of Service and - Privacy Policy. + Privacy Policy. They outline how we work together, use your data, and protect your privacy – ensuring a safe and enjoyable experience for everyone.
So, take a quick moment to review these documents. Once you're all set, the world of MOD is yours to explore. @@ -396,9 +396,9 @@

Terms of Service for MOD online components

Please review our - Terms of Service + Terms of Service and - Privacy Policy before continuing.
+ Privacy Policy before continuing.
While you can freely use your MOD unit without accepting these terms, you must agree to them in order to enable or perform any online interaction.

From 661518336c1222638d5ca4dbc79fb02dd40d1eef Mon Sep 17 00:00:00 2001 From: falkTX Date: Tue, 13 Feb 2024 11:44:24 +0100 Subject: [PATCH 24/31] Add terms directly within mod-ui Signed-off-by: falkTX --- html/desktop-pp.html | 207 ++++++++++++++++++++++++++++++++++++++ html/desktop-tou.html | 225 ++++++++++++++++++++++++++++++++++++++++++ html/index.html | 12 +-- 3 files changed, 438 insertions(+), 6 deletions(-) create mode 100644 html/desktop-pp.html create mode 100644 html/desktop-tou.html diff --git a/html/desktop-pp.html b/html/desktop-pp.html new file mode 100644 index 00000000..a5635d7f --- /dev/null +++ b/html/desktop-pp.html @@ -0,0 +1,207 @@ + + + + + + +MOD + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+
+
+

MOD Desktop - Privacy Policy

+
+
+ +
+
+ +

Introduction

+

+ Thank you for choosing MOD Desktop. Our commitment to your privacy is paramount, and we aim to be transparent about how we handle data related to your use of MOD Desktop. + Protecting your privacy and ensuring you understand our practices is crucial to us. +

+ +

Data Collection and Use

+

+ MOD Desktop is dedicated to enhancing your user experience while respecting your privacy. + To achieve this, we exclusively utilize Matomo analytics for collecting non-personal usage data. + Choosing Matomo is a deliberate decision to ensure we have full control over the collected information, without relying on third-party platforms such as Google, Facebook, or others. + This approach allows us to maintain the highest standards of privacy and data security by keeping this data in-house. +

+

+ We collect data on the following events to understand usage patterns and improve our service: +

+
    +
  • App Open: Tracks each time the application is opened, helping us gauge application usage frequency.
  • +
  • Plugin Load: Records the loading of plugins by name to identify popular plugins and inform our development focus.
  • +
  • Pedalboard Save: Monitors when pedalboards are saved, capturing usage patterns without storing any pedalboard names.
  • +
  • Pedalboard Load: Tracks the loading of pedalboards, similar to saves, without collecting pedalboard names.
  • +
+

+ This collected data is purely for internal use to enhance the MOD Desktop experience and is strictly non-personal. + Our commitment to your privacy means we prioritize the security and integrity of this usage data, ensuring it remains confidential and is used solely for the purposes outlined above. +

+ +

Data Sharing and Disclosure

+

+ We firmly stand by our commitment to not sell, share, or disclose the collected data to any third parties, except as strictly required by law. + Our use of Matomo enables us to uphold this commitment by providing us complete oversight and control over the analytics data, ensuring it is protected from unauthorized access or use. +

+ +

Data Security

+

+ The security of your data is a top priority. + We implement robust security measures to safeguard the non-personal information collected through our analytics. + While we strive for the highest level of security, it’s important to recognize that no method of electronic storage or transmission over the Internet is entirely secure. + We are committed to using commercially acceptable means to protect your data to ensure its security to the best of our ability. +

+ +

Changes to the Privacy Policy

+

+ We reserve the right to modify or update our Privacy Policy at any time. + Such changes will be communicated through our application and/or website, encouraging you to review our Privacy Policy periodically for any updates. +

+ +

Contact information

+

+ Should you have any inquiries or concerns about our Privacy Policy, please do not hesitate to reach out to us through our designated support channels. + We are here to ensure your experience with MOD Desktop is secure, private, and enjoyable. +

+

Owner and Data Controller:

+
    +
  • + Gianfranco Ceccolini
    + MOD Audio UG (haftungsbeschränkt)
    + Ernst-Augustin-Straße 9
    12489 Berlin
    + Germany +
  • +
  • Owner contact email: gdpr@mod.audio
  • +
+
+
+
+
+ + diff --git a/html/desktop-tou.html b/html/desktop-tou.html new file mode 100644 index 00000000..d7db23b6 --- /dev/null +++ b/html/desktop-tou.html @@ -0,0 +1,225 @@ + + + + + + +MOD + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+
+
+

MOD Desktop - Terms of Use

+
+
+ +
+
+ +

Introduction

+

+ Welcome to MOD Desktop. + These Terms of Use (“Terms”) govern your access to and use of MOD Desktop, an open-source application governed by the GNU Affero General Public License version 3 (AGPL-3.0). + By accessing or using MOD Desktop, you agree to be bound by these Terms and the AGPL-3.0 under which MOD Desktop is licensed. +

+

+ This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. +

+
+
+ +
+ Copyright © 2007 Free Software Foundation, Inc. <https://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. +
+ +
+
+ +

Acceptance of Terms

+

+ By using MOD Desktop, you acknowledge that you have read, understood, and agree to be bound by these Terms and the AGPL-3.0. If you do not agree with any part of these Terms, you may not use MOD Desktop. +

+ +

License and Use Rights

+

+ MOD Desktop is provided under the AGPL-3.0 license, which allows you to use, modify, and distribute the software under the same license. + The AGPL-3.0 is designed to ensure that all users have the freedom to use, study, share, and modify the software. + You are encouraged to review the full text of the AGPL-3.0 to understand your rights and obligations under this license. +

+ +

Modifications and Contributions

+
    +
  • Modifications: You may modify MOD Desktop for personal or community use, subject to the AGPL-3.0’s terms. Any modifications must also be made available under the AGPL-3.0.
  • +
  • Contributions: Contributions to MOD Desktop are welcome and encouraged. By submitting contributions, you agree to license them under the AGPL-3.0, allowing them to be freely used, modified, and shared by the community.
  • +
+ +

Prohibited Use

+

+ You may not use MOD Desktop for any unlawful or prohibited purpose. + You agree not to use MOD Desktop in a way that could damage, disable, overburden, or impair the software or interfere with any other party’s use and enjoyment of MOD Desktop. +

+ +

Intellectual Property

+

+ MOD Desktop and its original content, features, and functionality are and will remain the exclusive property of MOD Desktop’s developers and its contributors. + Your use of MOD Desktop does not grant you ownership rights to the software or its content. +

+ +

Disclaimer of Warranties

+

+ MOD Desktop is provided “AS IS,” without warranty of any kind, express or implied. + The developers of MOD Desktop expressly disclaim all warranties, including, but not limited to, the implied warranties of merchantability and fitness for a particular purpose. +

+ +

Limitation of Liability

+

+ In no event shall the developers or contributors of MOD Desktop be liable for any direct, indirect, incidental, special, consequential, or punitive damages arising out of or in connection with your use of MOD Desktop. +

+ +

Changes to Terms

+

+ We reserve the right to modify or replace these Terms at any time. We will provide notice of any changes by posting the new Terms on the MOD Desktop website or within the application itself. +

+ +

Governing Law

+

+ These Terms will be governed by and interpreted in accordance with the laws of the jurisdiction in which MOD Desktop’s primary developers are located, without regard to its conflict of law provisions. +

+ +

Contact Us

+

+ If you have any questions about these Terms, please contact us through the designated channels provided by MOD Desktop. +

+ +
+
+
+
+ + diff --git a/html/index.html b/html/index.html index 084a443b..3a7606ab 100644 --- a/html/index.html +++ b/html/index.html @@ -375,9 +375,9 @@

Welcome to MOD Desktop!

We're thrilled to have you on board and can't wait for you to explore all the amazing features we've built for you. Before you dive in, there's just a little bit of housekeeping to take care of.
By using this application you're agreeing to our - Terms of Service + Terms of Use and - Privacy Policy. + Privacy Policy. They outline how we work together, use your data, and protect your privacy – ensuring a safe and enjoyable experience for everyone.
So, take a quick moment to review these documents. Once you're all set, the world of MOD is yours to explore. @@ -385,20 +385,20 @@

Welcome to MOD Desktop!

{% else %} -

Terms of Service for MOD online components

+

Terms of Use for MOD online components

×

(NOTE: THIS AN EXAMPLE TEXT YET TO BE REVIEWED)
Your MOD unit can receive firmware updates over the internet, and also download new plugins and pedalboards.
- The use of these online services is under Terms of Service and Privacy Policy in order to comply with different regulatory laws across the globe.
+ The use of these online services is under Terms of Use and Privacy Policy in order to comply with different regulatory laws across the globe.

Please review our - Terms of Service + Terms of Use and - Privacy Policy before continuing.
+ Privacy Policy before continuing.
While you can freely use your MOD unit without accepting these terms, you must agree to them in order to enable or perform any online interaction.

From 0595788f54ade32e58b897d4292d865fd805b972 Mon Sep 17 00:00:00 2001 From: falkTX Date: Tue, 20 Feb 2024 12:10:25 +0100 Subject: [PATCH 25/31] Fix showing partial cpu stats Signed-off-by: falkTX --- html/js/host.js | 4 ++++ mod/host.py | 13 ++++++++----- mod/webserver.py | 5 ++++- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/html/js/host.js b/html/js/host.js index 66ebd76b..c377e435 100644 --- a/html/js/host.js +++ b/html/js/host.js @@ -119,6 +119,10 @@ $('document').ready(function() { $("#mod-cpu-stats").html(sprintf("%.1f GHz / %d °C", parseInt(cpufreq)/1000000, parseInt(cputemp)/1000)) + } else if (cpufreq !== "0") { + $("#mod-cpu-stats").html(sprintf("%.1f GHz", parseInt(cpufreq)/1000000)) + } else if (cputemp !== "0") { + $("#mod-cpu-stats").html(sprintf("%d °C", parseInt(cputemp)/1000)) } return } diff --git a/mod/host.py b/mod/host.py index ed70a140..f252d293 100644 --- a/mod/host.py +++ b/mod/host.py @@ -4774,11 +4774,14 @@ def get_free_memory_value(self): def get_system_stats_message(self): memload = self.get_free_memory_value() cpufreq = read_file_contents(self.cpufreqfile, "0") - try: - cputemp = read_file_contents(self.thermalfile, "0") - except OSError: - cputemp = "0" - self.thermalfile = None + cputemp = "0" + + if self.thermalfile is not None: + try: + cputemp = read_file_contents(self.thermalfile, "0") + except OSError: + self.thermalfile = None + return "sys_stats %s %s %s" % (memload, cpufreq, cputemp) def memtimer_callback(self): diff --git a/mod/webserver.py b/mod/webserver.py index 27cc4928..f61be889 100644 --- a/mod/webserver.py +++ b/mod/webserver.py @@ -1977,11 +1977,14 @@ def post(self): index = freqs.index(cur_freq) + 1 if index >= len(freqs): index = 0 + next_freq = freqs[index] + if cur_freq == next_freq: + return self.write(True) with open("/sys/devices/system/cpu/online", 'r') as fh: num_start, num_end = tuple(int(i) for i in fh.read().strip().split("-")) for num in range(num_start, num_end+1): with open("/sys/devices/system/cpu/cpu%d/cpufreq/scaling_setspeed" % num, 'w') as fh: - fh.write(freqs[index]) + fh.write(next_freq) self.write(True) class SaveSingleConfigValue(JsonRequestHandler): From a1e043cb5886fb61d193fd97ec49b280e63f1a5e Mon Sep 17 00:00:00 2001 From: falkTX Date: Thu, 14 Mar 2024 11:06:29 +0100 Subject: [PATCH 26/31] Dynamic server name via MOD_DESKTOP_SERVER_NAME Signed-off-by: falkTX --- utils/utils_jack.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/utils/utils_jack.cpp b/utils/utils_jack.cpp index 62de8b56..e5bf2cd9 100644 --- a/utils/utils_jack.cpp +++ b/utils/utils_jack.cpp @@ -201,7 +201,10 @@ bool init_jack(void) #ifdef _MOD_DESKTOP const jack_options_t options = static_cast(JackNoStartServer|JackUseExactName|JackServerName); - jack_client_t* const client = jack_client_open("mod-ui", options, nullptr, "mod-desktop"); + const char* servername = std::getenv("MOD_DESKTOP_SERVER_NAME"); + if (servername == nullptr) + servername = "mod-desktop"; + jack_client_t* const client = jack_client_open("mod-ui", options, nullptr, servername); #else const jack_options_t options = static_cast(JackNoStartServer|JackUseExactName); jack_client_t* const client = jack_client_open("mod-ui", options, nullptr); From 5d52614d634a31cf7409ba537160b9baf21cfe6e Mon Sep 17 00:00:00 2001 From: falkTX Date: Sat, 23 Mar 2024 09:24:10 +0100 Subject: [PATCH 27/31] Fix crash when manually fetching author homepage from ttl Signed-off-by: falkTX --- utils/utils_lilv.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/utils_lilv.cpp b/utils/utils_lilv.cpp index a18af187..28aa671f 100644 --- a/utils/utils_lilv.cpp +++ b/utils/utils_lilv.cpp @@ -2210,7 +2210,7 @@ const PluginInfo& _get_plugin_info(LilvWorld* const w, { if (LilvNode* const mntnr = lilv_world_get(w, lilv_nodes_get_first(nodes2), ns.doap_maintainer, nullptr)) { - if (LilvNode* const hmpg = lilv_world_get(w, lilv_nodes_get_first(mntnr), ns.foaf_homepage, nullptr)) + if (LilvNode* const hmpg = lilv_world_get(w, mntnr, ns.foaf_homepage, nullptr)) { info.author.homepage = strdup(lilv_node_as_string(hmpg)); lilv_node_free(hmpg); From 2fe8f732fa759aa61876d0f358f3b52937791e3e Mon Sep 17 00:00:00 2001 From: falkTX Date: Sat, 28 Sep 2024 19:02:41 +0200 Subject: [PATCH 28/31] Make sure PEDALBOARD_TMP_DIR exists when starting Signed-off-by: falkTX --- mod/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mod/__init__.py b/mod/__init__.py index 3998841e..879f63c2 100644 --- a/mod/__init__.py +++ b/mod/__init__.py @@ -68,6 +68,9 @@ def check_environment(): else: os.makedirs(PEDALBOARD_TMP_DIR) + else: + os.makedirs(PEDALBOARD_TMP_DIR) + # remove temp files for path in (CAPTURE_PATH, PLAYBACK_PATH, UPDATE_CC_FIRMWARE_FILE): if os.path.exists(path): From 41c86c5d9d12398ddb399b1f7784703b6a342242 Mon Sep 17 00:00:00 2001 From: falkTX Date: Sat, 28 Sep 2024 19:10:41 +0200 Subject: [PATCH 29/31] Fix a lint error Signed-off-by: falkTX --- mod/host.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/mod/host.py b/mod/host.py index f252d293..d642c49f 100644 --- a/mod/host.py +++ b/mod/host.py @@ -3512,10 +3512,11 @@ def load(self, bundlepath, isDefault=False, abort_catcher=None): p.split(":",1)[-1]) for p in get_jack_hardware_ports(False, True)) else: - mappedOldMidiIns = {} - mappedOldMidiOuts = {} - mappedNewMidiIns = {} - mappedNewMidiOuts = {} + mappedOldMidiIns = {} + mappedOldMidiOuts = {} + mappedOldMidiOuts2 = {} + mappedNewMidiIns = {} + mappedNewMidiOuts = {} curmidisymbols = [] for port_symbol, port_alias, _ in self.midiports: From 74a967c8c68a2ab16815ba6eb3664af75af51676 Mon Sep 17 00:00:00 2001 From: falkTX Date: Thu, 7 Nov 2024 14:19:13 +0100 Subject: [PATCH 30/31] Allow RemotePluginWebSocket connections from localhost:8010 Signed-off-by: falkTX --- mod/webserver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mod/webserver.py b/mod/webserver.py index f61be889..eb53e781 100644 --- a/mod/webserver.py +++ b/mod/webserver.py @@ -1145,7 +1145,7 @@ def check_origin(self, origin): protocol, domain = match.groups() if protocol not in ("http", "https"): return False - if domain != "mod.audio" and not domain.endswith(".mod.audio"): + if domain != "localhost:8010" and domain != "mod.audio" and not domain.endswith(".mod.audio"): return False return True From 8a92ab20cfa695d72212b83cf714b7c2eac78bb5 Mon Sep 17 00:00:00 2001 From: falkTX Date: Thu, 14 Nov 2024 21:55:49 +0200 Subject: [PATCH 31/31] Use soundcard value for load/save mixer profile, not platform Signed-off-by: falkTX --- mod/host.py | 2 +- mod/profile.py | 45 +++++++++++++++++++++++---------------------- 2 files changed, 24 insertions(+), 23 deletions(-) diff --git a/mod/host.py b/mod/host.py index d642c49f..16dec2aa 100644 --- a/mod/host.py +++ b/mod/host.py @@ -6996,7 +6996,7 @@ def profile_apply(self, values, isIntermediate): # skip alsamixer related things on intermediate/boot if not isIntermediate: - apply_mixer_values(values, self.descriptor.get("platform", None)) + apply_mixer_values(values) if self.hmi.initialized: try: diff --git a/mod/profile.py b/mod/profile.py index 6809f75b..d721a030 100644 --- a/mod/profile.py +++ b/mod/profile.py @@ -24,12 +24,13 @@ def ensure_data_index_valid(data, fallback): if not isinstance(index, int) or index < 1 or index > Profile.NUM_PROFILES: data['index'] = fallback -def apply_mixer_values(values, platform): +def apply_mixer_values(values): if not os.path.exists("/usr/bin/mod-amixer"): return - if os.getenv("MOD_SOUNDCARD", None) is None: + soundcard = os.getenv("MOD_SOUNDCARD", None) + if soundcard is None: return - if platform == "duo": + if soundcard == "MODDUO": os.system("/usr/bin/mod-amixer in 1 dvol %f" % values['input1volume']) os.system("/usr/bin/mod-amixer in 2 dvol %f" % values['input2volume']) os.system("/usr/bin/mod-amixer out 1 dvol %f" % values['output1volume']) @@ -38,7 +39,7 @@ def apply_mixer_values(values, platform): os.system("/usr/bin/mod-amixer hp byp %s" % Profile.value_to_string('headphoneBypass', values['headphoneBypass'])) return - if platform == "duox": + if soundcard == "DUOX": os.system("/usr/bin/mod-amixer in 1 xvol %f" % values['input1volume']) os.system("/usr/bin/mod-amixer in 2 xvol %f" % values['input2volume']) os.system("/usr/bin/mod-amixer out 1 xvol %f" % values['output1volume']) @@ -48,24 +49,25 @@ def apply_mixer_values(values, platform): os.system("/usr/bin/mod-amixer cvexp %s" % Profile.value_to_string('inputMode', values['inputMode'])) os.system("/usr/bin/mod-amixer exppedal %s" % Profile.value_to_string('expPedalMode', values['expPedalMode'])) return - if platform == "dwarf": + if soundcard == "DWARF": os.system("/usr/bin/mod-amixer in 1 xvol %f" % values['input1volume']) os.system("/usr/bin/mod-amixer in 2 xvol %f" % values['input2volume']) os.system("/usr/bin/mod-amixer out 1 xvol %f" % values['output1volume']) os.system("/usr/bin/mod-amixer out 2 xvol %f" % values['output2volume']) os.system("/usr/bin/mod-amixer hp xvol %f" % values['headphoneVolume']) return - if platform is None: - logging.error("[profile] apply_mixer_values called without platform") + if soundcard is None: + logging.error("[profile] apply_mixer_values called without soundcard") else: - logging.error("[profile] apply_mixer_values called with unknown platform %s", platform) + logging.error("[profile] apply_mixer_values called with unknown soundcard %s", soundcard) -def fill_in_mixer_values(data, platform): +def fill_in_mixer_values(data): if not os.path.exists("/usr/bin/mod-amixer"): return - if os.getenv("MOD_SOUNDCARD", None) is None: + soundcard = os.getenv("MOD_SOUNDCARD", None) + if soundcard is None: return - if platform == "duo": + if soundcard == "MODDUO": data['input1volume'] = float(getoutput("/usr/bin/mod-amixer in 1 dvol").strip()) data['input2volume'] = float(getoutput("/usr/bin/mod-amixer in 2 dvol").strip()) data['output1volume'] = float(getoutput("/usr/bin/mod-amixer out 1 dvol").strip()) @@ -74,7 +76,7 @@ def fill_in_mixer_values(data, platform): data['headphoneBypass'] = Profile.string_to_value('headphoneBypass', getoutput("/usr/bin/mod-amixer hp byp").strip()) return - if platform == "duox": + if soundcard == "DUOX": data['input1volume'] = float(getoutput("/usr/bin/mod-amixer in 1 xvol").strip()) data['input2volume'] = float(getoutput("/usr/bin/mod-amixer in 2 xvol").strip()) data['output1volume'] = float(getoutput("/usr/bin/mod-amixer out 1 xvol").strip()) @@ -85,17 +87,17 @@ def fill_in_mixer_values(data, platform): data['expPedalMode'] = Profile.string_to_value('expPedalMode', getoutput("/usr/bin/mod-amixer exppedal").strip()) return - if platform == "dwarf": + if soundcard == "DWARF": data['input1volume'] = float(getoutput("/usr/bin/mod-amixer in 1 xvol").strip()) data['input2volume'] = float(getoutput("/usr/bin/mod-amixer in 2 xvol").strip()) data['output1volume'] = float(getoutput("/usr/bin/mod-amixer out 1 xvol").strip()) data['output1volume'] = float(getoutput("/usr/bin/mod-amixer out 2 xvol").strip()) data['headphoneVolume'] = float(getoutput("/usr/bin/mod-amixer hp xvol").strip()) return - if platform is None: - logging.error("[profile] fill_in_mixer_values called without platform") + if soundcard is None: + logging.error("[profile] fill_in_mixer_values called without soundcard") else: - logging.error("[profile] fill_in_mixer_values called with unknown platform %s", platform) + logging.error("[profile] fill_in_mixer_values called with unknown soundcard %s", soundcard) # The user profile models environmental context. # That is all settings that are related to the physical hookup of the device. @@ -225,10 +227,9 @@ def value_to_string(cls, key, value): return "" def __init__(self, applyFn, hwdescriptor): - self.applyFn = applyFn - self.platform = hwdescriptor.get("platform", None) - self.changed = False - self.values = self.DEFAULTS.copy() + self.applyFn = applyFn + self.changed = False + self.values = self.DEFAULTS.copy() if os.path.exists(self.INTERMEDIATE_PROFILE_PATH): data = safe_json_load(self.INTERMEDIATE_PROFILE_PATH, dict) @@ -241,7 +242,7 @@ def __init__(self, applyFn, hwdescriptor): except IOError: pass - fill_in_mixer_values(self.values, self.platform) + fill_in_mixer_values(self.values) IOLoop.instance().add_callback(self.apply_first) # ----------------------------------------------------------------------------------------------------------------- @@ -411,7 +412,7 @@ def store(self, index): self.values['index'] = index # request and store mixer values - fill_in_mixer_values(self.values, self.platform) + fill_in_mixer_values(self.values) # save intermediate file first with TextFileFlusher(self.INTERMEDIATE_PROFILE_PATH) as fh: