fix: pad armed receive on group, lua guards
CI build oscope-of / build-check (push) Has been cancelled
CI build oscope-of / build-check (push) Has been cancelled
Move /armed/<name> 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.
This commit is contained in:
@@ -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.
|
||||
|
||||
Vendored
BIN
Binary file not shown.
@@ -82,7 +82,6 @@ def _pad_cell(name, x, y, w, h):
|
||||
[s.prop("name", name, "s"), s.frame(0, 0, w - 20, btn_h)],
|
||||
messages=[
|
||||
s.osc("/launch", [s.const(name), s.vali()]),
|
||||
s.osc(f"/armed/{name}", [s.val()], send=0, receive=1, feedback=1),
|
||||
],
|
||||
)
|
||||
vol_fader = s.node(
|
||||
@@ -99,6 +98,7 @@ def _pad_cell(name, x, y, w, h):
|
||||
s.script(_lua("pad.lua")),
|
||||
],
|
||||
children=[arm_btn, vol_fader, name_label],
|
||||
messages=[s.osc(f"/armed/{name}", [s.val()], send=0, receive=1, feedback=1)],
|
||||
)
|
||||
|
||||
|
||||
@@ -126,8 +126,7 @@ def live_page():
|
||||
# Master VU fader (receive-only /sync/rms)
|
||||
ch.append(s.node(
|
||||
"FADER",
|
||||
[s.prop("name", "rms", "s"), s.frame(490, 8, 40, 72),
|
||||
s.script(_lua("feedback.lua"))],
|
||||
[s.prop("name", "rms", "s"), s.frame(490, 8, 40, 72)],
|
||||
messages=[s.osc("/sync/rms", [s.val()], send=0, receive=1, feedback=1)],
|
||||
))
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
-- On value change: read all 8 step faders (m0..m7) -> /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
|
||||
|
||||
@@ -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/<name> 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 <messages> 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)}"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user