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
|
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
|
re-routing the audio. The master RMS VU + beat + per-pad armed reflection
|
||||||
cover the live-feedback need.
|
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)],
|
[s.prop("name", name, "s"), s.frame(0, 0, w - 20, btn_h)],
|
||||||
messages=[
|
messages=[
|
||||||
s.osc("/launch", [s.const(name), s.vali()]),
|
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(
|
vol_fader = s.node(
|
||||||
@@ -99,6 +98,7 @@ def _pad_cell(name, x, y, w, h):
|
|||||||
s.script(_lua("pad.lua")),
|
s.script(_lua("pad.lua")),
|
||||||
],
|
],
|
||||||
children=[arm_btn, vol_fader, name_label],
|
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)
|
# Master VU fader (receive-only /sync/rms)
|
||||||
ch.append(s.node(
|
ch.append(s.node(
|
||||||
"FADER",
|
"FADER",
|
||||||
[s.prop("name", "rms", "s"), s.frame(490, 8, 40, 72),
|
[s.prop("name", "rms", "s"), s.frame(490, 8, 40, 72)],
|
||||||
s.script(_lua("feedback.lua"))],
|
|
||||||
messages=[s.osc("/sync/rms", [s.val()], send=0, receive=1, feedback=1)],
|
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.
|
-- On value change: read all 8 step faders (m0..m7) -> /seq/melody/set.
|
||||||
-- API verified on GrosMac at fidelity gate.
|
-- API verified on GrosMac at fidelity gate.
|
||||||
local LO, HI = -7, 14
|
local LO, HI = -7, 14
|
||||||
|
local unpack = table.unpack or unpack -- Lua 5.1 / LuaJIT compat
|
||||||
|
|
||||||
function onValueChanged(key)
|
function onValueChanged(key)
|
||||||
if key ~= "x" then return end
|
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
|
local fx = (f and f.values and f.values.x) or 0
|
||||||
degs[i] = math.floor(LO + (HI - LO) * fx + 0.5)
|
degs[i] = math.floor(LO + (HI - LO) * fx + 0.5)
|
||||||
end
|
end
|
||||||
sendOSC("/seq/melody/set", table.unpack(degs))
|
sendOSC("/seq/melody/set", unpack(degs))
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -247,3 +247,53 @@ def test_seq_page_no_sync_amp():
|
|||||||
page = layout.seq_page()
|
page = layout.seq_page()
|
||||||
addrs = _addrs(page)
|
addrs = _addrs(page)
|
||||||
assert "/sync/amp" not in addrs, "/sync/amp found — per-voice VU was supposed to be omitted"
|
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