From 67fd253de59e43bdf114c959940e2ed9e97759ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=27=C3=A9lectron=20rare?= <108685187+electron-rare@users.noreply.github.com> Date: Sun, 28 Jun 2026 13:49:00 +0200 Subject: [PATCH] fix: pad armed receive on group, lua guards Move /armed/ receive msg from arm BUTTON to parent GROUP so pad.lua onReceiveOSC fires on the node that owns the message (FIX 1). Remove feedback.lua from master RMS fader; native receive already drives the VU, onFrame decay was pure downside (FIX 3). Add Lua 5.1/LuaJIT compat shim for table.unpack in melody_faders (FIX 2). Document deferred sequencer playhead in README (FIX 4). Regression test: 16 pad GROUPs carry /armed/* receive; 21 tests pass. Rebuild dist/av-live-control.tosc. --- touchosc/README.md | 4 +++ touchosc/dist/av-live-control.tosc | Bin 7933 -> 7930 bytes touchosc/gen/layout.py | 5 ++- touchosc/gen/lua/melody_faders.lua | 3 +- touchosc/tests/test_layout.py | 50 +++++++++++++++++++++++++++++ 5 files changed, 58 insertions(+), 4 deletions(-) diff --git a/touchosc/README.md b/touchosc/README.md index 80e663a..76b4b12 100644 --- a/touchosc/README.md +++ b/touchosc/README.md @@ -93,3 +93,7 @@ Feedback (SC → surface, port 9000, all created by `touchosc_feedback.scd`): voices to bus 0, so per-voice live levels are not producible without re-routing the audio. The master RMS VU + beat + per-pad armed reflection cover the live-feedback need. +- **Sequencer playhead / step-index highlight** (spec §2/§4/§7) is + **deferred**: the data-only engine does not emit a step index over OSC, so + adding column-highlight requires a new SC step-emit plus a layout + column-highlight receiver. Not built in this pass. diff --git a/touchosc/dist/av-live-control.tosc b/touchosc/dist/av-live-control.tosc index 022c1e1183b694d5b35993563ddb473fc0289ead..9827522ca68143a045d39f0772911347c6c36cd6 100644 GIT binary patch literal 7930 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 GsQ&@-UszrM literal 7933 zcmZvgRZtvSmxXbsad)?d;7)LtK;!OijW+J??veyea1A85hQ{69g1ZNpd;gh-nW}j| zwa!`VtF`OYriw>FF?pA8fO|LhW_Pmpwr00i@J^{|9FGk$R;lTUjS3m%j} zURxo{p1X)%yq9##7|>gVjwrc=%Yy$@Fyzp-(4Q>{uDzb=jz`Okcss>YXyi4UDNDPT zS1cRfX$Q_~&ttvqo{fC=PajI(oxa>Q{lovHoE>G3pB(*x>TSQG|DxybzBpyzomq;` zQ+#7B59LdEq80UZswP~G$f7kG@}IBb#2SbZ*QY#>rpj~lT>ZEq&K|qX%)*m z3$tT!&SzSS($wXocU69`cVbl@Ya_G!@!mC=xn-w!!Zn$aT#TDMIf*jx4?v_!tT<5g z$eU73P%%B$Uq~DvKO8Qo)}KiO9hn|zhX0-mOH%@$4j#b2GGPARl;JHTBYqy#4FM0* z2oHWtaGmWzITnr0gHJRFrQQg+gNB)DS@n6Sh0diER(uND_@%mwt2#4)3@d4cOiPBG zlnRr_c3d`-z|u;dec|2R+1R5pJ91>V9{Oc8l`B4VI|7I#m&TSfT7q1nD8cL=l(N&$ z{&QY+1{}nxMneM-U6UI5%_4izSGA0e5M_?0DN~8sHAEHHS(KD)`W2)ykpx{IO5Vu& zxXopPmpsal>|OkRZFcx~KfJK%1~RcK=rvn%P4;qzv_ub>C>eg4G$lX(C_Zol9i7>O zW2Wj96!p3O`=Z}8`h&2)l?PlGy=`~6gP;;dR%$!kPitvA91o!Y++G`t4#67TTi9-} z2?RYo(_>bok5c^geK`8$PxWUV^63GBB|g{w;{u`(*e0Kll6-4lCLki1`S=pxkPTYowjHNYm^7vYxjK%h25z&BmyfDdj!PP$1*_A5M$wqX~B%ZUI5`0N&o8}(e-q634VVfi2idm+qoX@@^Cck z6&=gtjY?DLgmRUOf`c@Y$VNAyQr#uPCPr#CX$d0*kGRc^Xj_O#Ag!aKF_KL^-@cJG|>7GN#XSax7-ey#1?{!Ja%6~Hzd2wDWy~Phh2xkLawGq#Q z=Dosk;*=pPVyA?*f79Rm?)jmkR{MB5AB86NPovitCid1jPc!Q3wY$c!#ZS}`k$puH zkV?2r9@_qPN?&-z2k!^jb1G)%^$3~To4XyR&y4QgK!(jfta}X9usnsoeE~JILldthFvuO5dQ4=A{rM%NLrp@Qt z=!U3CJLK5Mec`$Q|J`}RlW4dyp?{+L#M{Pr_35Ga*429I{dND=RaeFp{|F1-#~u=E zxBvJyI^q~?TcvWe(COlMat=3Ti-|8$1hKDL-6%5kC&>~w!_F!;FRn{kT8c%N7W{SE zDJgcM`mkYZTOYs9Oc(nZNL1n7G;{z~Y0d4@g~`v@3-oh7)3+3#Ew*NltG{a3g z!rsYK_K=>z;Z4Ag>W9|Th6xx+gV*rJxp(!oeSd?b(?5vk+8nDSGdL#e23=f1shm1% z=UUKd-CBYIi!cMP|AH@GI>S#IzKT6WA4nLRA3yJ6lgRpP8HFw4QhddxB_5__p3E`J z7*x?tjE-`Z3ugu3XkK6A7CQ(&=O%yKtgin3PSVgE_Ut?gMPruX=P$v-iqRLK?$7toxOS%z^c{0?p6Dx#XI-%h#;H+9*~5!bLVh zh$KB}{4Z5tZQtBo^q{YF$EXg>4;q^IU8L$=1bXcORu?=!l@>iCOk!tXZ7}eBrT&(& zQIIHyQOJ#x(3l9v8EKdgez#*HLs?n2ws*(jnUl=MO|qhzg+PLtePA(%+Xt6BbBOJz z$znchr?025mV#E}x1pt5;vgU411w=h4bz-8qP_t@iU+C44K|6`IoD%sSSxaHUVVRP z$8T_lBSyy3?MLjOcFs@A2Ejrr-n?+M(9qw#A<8OY+o^_19dXO^7H)iI%?)jD4Q-D7 zGi2?A*$OkMw9d1@61;TGr7_IU#TVh3qPa2-^=)|{$D;JfCuM$`p~Y(g5ZnbWM>M8V z^=vy-^_LBRfKM1FfPAjjhPI&{()btZcuSMWJrGh&CM-=Q9B(6BMEnqIHa)=?wD_to z(8R&JxIts=*r6R4y!a|kuUB)oX^)a-Z|XuxjIutOP-O(Ylo?z#LA3yOLS1+{viK9K z_+R|M&RF0CXuHgc@HN}$n3{RdLxl&0o2;0dZLSn4{vXm#!e6{hETpoL)i!G{##Yzv#$aGdIC}edB7pB zmqY=e0F*9UnT37W=T@DKy@+B&2TtrUrUhqtFf{962LRn9=0X++=t6n=e9OqKy>zx6 z{WiOZ z+V+sGdI4#9L!n4Pt715-_Lzn^UCZ! zP*5HV;$SYe+=>HQh{=4{sBaiQ{b&=6#MX@3AikS6MLO8 z6kKUTHI;5GSY==>a$Af>wH&JOeg)hMr>3^ zyoQ)+hkwqm(T1kENCl8jY^48!MPK@Z8du(x838qEmRh<#YHs&`hepQ=L+cX0W}PL+<1>+(kLz)R0amC9yz6x<8_-55b$%`plFIF+Kop>r{vwT<1SZI{=Y z@E0%}dhru3>KR$Ov02*$DzOS8jWA<{VIwl`q-F#CK3RKq0RE>DY!oiq|IQQbC-BK9 z<6u4VD7mmEiE;$mE18CEDjg zi#5vt_Ygk-h0;-UwLv?q&z$)t36|5OeLT!0v?v#;gVsN_%qTKkatE7Q0aSX7p?c1UA?=_%LL46H9SW;oNF|K-Y| z$g;?vVZDpg}>= zh%|${Dtcz+WASf7t6)jyPPr~sq%$M2=pn}G87#G17T~bckMB-Qt(zN-azv=bX_MRH zz!Ai=qP5e*f%yjCtBQ0I220h!$8l|!RwuRf|Fj`luwXyRU58&c4Z)Ze+Jin!qkf2q zIdsD8;MPXUxuRh7uCUf(ew1hwr96SI3`Ce9VWpO4Dp$uZt}Y!Bv;u0gYA)QG^LI9d zT*|SzN>qVnBXCU`QN-#?ovsy!z?8v1KPHNN%f|f=(8NJw8@CLh3U*{@>Mv9&UY{Ys zdS*}VX{0kt213Y;f)Sk?5qWa@(6$Rpw1_>v3rVJo{cWE8Zv#ebDe6eN8QA$G=DI7( zvSW?5^r0=%@KyYj4H-DwFs@#E_}naO!8(LSob6C%8l|G|u`+WsU?#C`*8wCBX<9Q7 zhkgedY`i-lwr$Mll966JzFSqEY2JN1gEp~??|I?RYGmOyJzr8i#!7afHn3?+Nr(KH zTQN;Bzq(p#lhw|>#I^IQYZdPFpH=6tt|ya5!GFkp&?7tJ$qX`}+!FRSfyk9&KDT6P z6zKXGW_M}#Ov!CN%Wu}A|5MdKyaZ*CH!d}Zr_|Ltc{K%B;S+}P$6;z|%x9hHz-1r& zGA5;23?(pZ7&79J0#cq}X`c||kxa|}j||3dCX_~&|8$-Y3F506ddf4ID>0!+2ZsdM zAV%h3utCLClhw3^t&QRq04&xqw4rlP>#fxoL{xD?Ubo%&$qI3=wmR;tDMCUYS|8=Y zW~kfH8aKF&FzKCDYNOHJ7Lh6hQb%R$oXQnL#v4zuh!A|(Sk%W`GRGJ=j)8;oo>u&0 z2W^+b**%E^0y>4WoN7XrBWza1h8-$9H!WBD9+lCHE^BtYear5a$a5$up?D3A9 zm~bg@NhPf9BW5L$O`^xOH4cUj{00v=pfZ*oKVrw{bAG+m3k|2IGiIQzSBv5+V_*=V zHs8pQjkkZE=O?KQD@`1~OCC3-D%g)+>ryoY2#tXei`Qnr{nerSSOAP?5kZ$FIJ_I_ljoiGgZ@h>hJS$bqI6`=Pjmh%x) zrHtDnNL4Vi^2uUkD5c=b6c!>mFnd^fyskKGg{koJL#pmcEC0#Q+#^Q&8= z5AV{5D}+O}zc^^~n-xn%UdUYsKIVR@%rCU5Ih-s}0fQsfgy%sT6JvPl4NaKuS)|n3 znjxQ9d#s-QehOi>={UT!%Tgt(a#Uf=jp%t#$q8F2V=9!h3$XaO7lm|Z0YpD81zt&u zVSXOdqd;?Xz>1$J{(-f{Qh&~ zJ#+7U-pyxzAh--hnU>Wq_WaWXb;9TtqBb?V^!;9QEOg;2^v&_&Xx8s~QYQVqs$HVH ztoJiZ&yyv-XF$#(DfTf*%&GBmPrlT>%!yrEOc&rS*rWc(%Y*B~`-$(vp06yUL_VdF zBGzBQc3rpIRj|iobg(?dpCM2-F*=RAVe6!Xe@X)EqzsI6E@m~5*TY1g;yzfox)Ljj zFYr(C$CSj}XzCVeMi@ytMZ#vML|{w^ajrK#)J(AI3Z-4tVl(*7%Jpns%KwRln=bg# za<5tys&6|*<916Q316WJ=Msw{M5@NhKuUgkLFKLPnlMpZg4&J{-&*>NS}*9nn?8NH zKHiQembAtt8yo31&sH*1p|ZPCtA>zB}z5OLp=D%Zla1PN`!25E7zH8X~J2 zaUqZ-C7{`B@rDY`v)X4t6Ok!0Zi|!bkmC0*y@wDwCdowlnCHpvpy*=^DR@(DcL#p+sR?4CsMcU+6 zB|0w(Nr1byr}_Jpx%_c1(olA9Zx*liqm(vr&l|6z925#Kj#l6O2}Jab>v>(H)o*! z=Wzuw-u~ZX@Leoo*fLyk2(X?O-x1CoE-}RD3DVRzG;)nIaeXtPFwCu?g0iW6sG!VnXPk@8stKvQ#KVD|5$MiZ=h0$j$Y`=^v@s2$Pj`eiIMsNH2 z8H&g`GMbxaIFecI}J9*a-AoiyYd*n-hjPcf7vK_D~UPx|z#C2@RE;m)O($?**L=dTW(A7;9LYLX9G zk8CVfG-w@BP<7^07LJ`v0t!Bb`${gS^AZn2jJ>%Me?>O3qd!kTUnbD5&Q)f*y${$= z$Kv0zo_`KsomDeC2?cq1^9qRw3yT=&?3@YbyyiaGH=fQ_i?8=mI9)pWJ)B0}bOr83 zr8>C|AKu961|6JX$#JOt%rQyQGzhBN1!X@X)Co_U>cse%#5^3mb)H8vt@i_NJCow- zYIQUp`w=6>wO}3vA^p>yZyMjG;5iM$8MU{xxF7Xk5fY?b9PXO`Jy`c}I+B-5!0y zXmc}&!Hb($xz;)7^zP5L{;Vzvvu|~;I5d=WCFWPhV@%Heo#sP1*=F(jb6Y}RUHs%F zTl>7h*l5#LE79}2iRD7qRln9J{>B#- z3e~N2_%(Y|9ByQ1cUD)wegNJxtXg0F8h1L0WM|~1JX=|Ompd2 zj^1wWO7DkganaQ(6Y;c0@xQTou@TQ{+jcfb(=6Ea1+mxMT+)e+?P^c;@CMy}r3MQc zgo#~&xI2dUeTZ4*?RcyT)N9nrbV&O}dVnlO3t7-y6>Fw7TU@8_r_*;%EtI@{t8_$p zBN+2swvzBlYN*1#igQ`UkMI`V^3nc+D706p$izXbN3#rUXa=qWM#NTEOu({4wz|2c zchRC`q@P7_VkNa+9MB$iprP?O3ssOEt_{0pDq*xmLN*9w4WPsN}a}f3S?Ai%7@{edXJM!vvv*SLSv|8<}y1FDk;OrgGx>G}o$) zG{84_8}Zm;5Wo7{w*ntw6Emcm-O}L)_`I3myR6ZMR2`$!02yGwi%n{0h6<`RFA*V%A zAM_s)uaA&9CA*~4-qpstL~on`Ldr_4Bs^NNwE7!`Ri;f7H_wB#C-(sID)xQQPYlUy zTM@GfXhWdJfb-FeILBJ}_YWO^v(d9}TKWUA4rrRoj$YP%ZdXg`R#&V2mtJ2@9(GI~ z`Zuw2keUR(0k=bEJ%w`zZ_lwr1OgT#d5Jx@D`oE2FaA0fytw#j_1S!b3-Di~X+0am z#&)xNbZ?BQ@5CO-Djy|uL`2&xj=U^d_aOXb=qG&fd^mhwi*WR2BzJms8b2f=7gS+$O!AV!n?sb-l2fQ<8BSmxmWsAamiCLt#Wse740GWu6?}v+6{P%JMDCFwC)hE0r$X2 z85eA2V;f9;DL_7c>lF}uy|S5W?CBbG=KFY#NRq2KI}K#bb6GD8g#XFPzerlPig!_I z*H4uNzid(XUXahMysHl!ZBfa9bB@sCOwa)530(wkjcEDmy*gbSz5O`3NG|z}^wlaH zR3LKFyClly=iHk{Es0!HjcvkZ$Z}DzKvp~?QHLwI*=O}Z%e6G^z=_)_Kqa=z_~sOxhNlu(K9DLk##QTG$&@#AL%HBswH(w|CV%yIk$Re)ZQU9J0t4 zu>%yhRjkDdM_}Z*7^}Uc42%cMNB@ss;&$>xuH_Ku?+HW=I45jW<}X=4KS$DgwLbZl zFHY9rvn$j><~xC02ILvfmFaCrau{hB-zT)z3p{-}hpiL?2NyN{HBa zb+^JZAGu3plhYU-tMt5#H7@^0&1POa^E+F|-kO7YE!Znp%%_~*2d4%r)}QX5AKV$G z=t /seq/melody/set. -- API verified on GrosMac at fidelity gate. local LO, HI = -7, 14 +local unpack = table.unpack or unpack -- Lua 5.1 / LuaJIT compat function onValueChanged(key) if key ~= "x" then return end @@ -12,5 +13,5 @@ function onValueChanged(key) local fx = (f and f.values and f.values.x) or 0 degs[i] = math.floor(LO + (HI - LO) * fx + 0.5) end - sendOSC("/seq/melody/set", table.unpack(degs)) + sendOSC("/seq/melody/set", unpack(degs)) end diff --git a/touchosc/tests/test_layout.py b/touchosc/tests/test_layout.py index 98dfd79..dff8728 100644 --- a/touchosc/tests/test_layout.py +++ b/touchosc/tests/test_layout.py @@ -247,3 +247,53 @@ def test_seq_page_no_sync_amp(): page = layout.seq_page() addrs = _addrs(page) assert "/sync/amp" not in addrs, "/sync/amp found — per-voice VU was supposed to be omitted" + + +# --------------------------------------------------------------------------- +# Pad armed receive on GROUP — FIX 1 regression guard +# --------------------------------------------------------------------------- + +def test_live_page_pad_groups_carry_armed_receive(): + """Each pad GROUP (carrying pad.lua) must have the /armed/ receive + message on itself — not only on the child arm BUTTON. pad.lua fires via + onReceiveOSC on the node that owns the matching receive message; if the + message lives only on the child BUTTON, the GROUP script never fires and + armed-state color reflection is dead.""" + page = layout.live_page() + + # Collect GROUP nodes that carry a script property (= pad GROUPs with pad.lua). + # The top-level LIVE GROUP has no script, so this selects exactly the 16 pads. + pad_groups = [] + for grp in page.findall(".//node[@type='GROUP']"): + for prop_el in grp.findall("properties/property"): + key_el = prop_el.find("key") + if key_el is not None and key_el.text == "script": + pad_groups.append(grp) + break + + assert len(pad_groups) == 16, ( + f"expected 16 pad GROUPs with a script property, got {len(pad_groups)}" + ) + + for grp in pad_groups: + # Resolve the pad name from its name property for a readable failure msg. + grp_name = "?" + for prop_el in grp.findall("properties/property"): + k = prop_el.find("key") + if k is not None and k.text == "name": + v = prop_el.find("value") + grp_name = v.text if v is not None else "?" + break + + # The GROUP's direct block (not descendants) must contain + # exactly one osc element with receive="1" whose path starts /armed/. + armed_msgs = [ + osc_el for osc_el in grp.findall("messages/osc") + if osc_el.get("receive") == "1" + and osc_el.find("path/partial/value") is not None + and (osc_el.find("path/partial/value").text or "").startswith("/armed/") + ] + assert len(armed_msgs) == 1, ( + f"pad GROUP '{grp_name}' must have exactly 1 /armed/* receive msg " + f"on the GROUP itself, got {len(armed_msgs)}" + )