From 495c5680bfbfe74f4d46aa4e76b9faaf45113a74 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 16:16:14 +0200 Subject: [PATCH] feat: tosc sections page for song build Add SECTIONS (page 4): 8 slot launch+save buttons, PREV/NEXT nav, /scene/state receive on the GROUP so sections.lua colors slots (green=current, blue=filled, dark=empty). Update pager to 4 pages, add 4 new tests (26 pass), rebuild dist/av-live-control.tosc. --- touchosc/dist/av-live-control.tosc | Bin 8482 -> 9627 bytes touchosc/gen/layout.py | 58 ++++++++++++++++++++++- touchosc/gen/lua/sections.lua | 23 +++++++++ touchosc/tests/test_layout.py | 73 ++++++++++++++++++++++++++++- 4 files changed, 151 insertions(+), 3 deletions(-) create mode 100644 touchosc/gen/lua/sections.lua diff --git a/touchosc/dist/av-live-control.tosc b/touchosc/dist/av-live-control.tosc index ae9fec41652bec1b58d3dbf782aa53d770a89cec..a6c0c6fef2661c9c9f59b960739358e8623972d0 100644 GIT binary patch literal 9627 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 Z6I7+t;LUDe;K>ul*qV05Ix7I}zW{|?R9651 literal 8482 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

G)!C4!f=XLvy*RCtoM$(oe6O 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", "/scene/next", 220, nav_y, 200, nav_h, args=[s.vali()])) + + return page + + # --------------------------------------------------------------------------- # Pager + root # --------------------------------------------------------------------------- @@ -335,6 +391,6 @@ def build(): root = s.node( "GROUP", [s.prop("name", "av-live-control", "s"), s.frame(0, 0, *CANVAS)], - children=[pager([live_page(), fx_page(), seq_page()])], + children=[pager([live_page(), fx_page(), seq_page(), sections_page()])], ) return root diff --git a/touchosc/gen/lua/sections.lua b/touchosc/gen/lua/sections.lua new file mode 100644 index 0000000..6c399d0 --- /dev/null +++ b/touchosc/gen/lua/sections.lua @@ -0,0 +1,23 @@ +-- SECTIONS GROUP: color the 8 launch slot buttons from /scene/state. +-- API verified on GrosMac at fidelity gate. +-- /scene/state ... +-- cur=-1 means no active section; fillN=1 means slot N is saved. +function onReceiveOSC(message, connections) + local path = message[1] + if path ~= "/scene/state" then return end + local args = message[2] + local cur = math.floor((args[1] and tonumber(args[1].value)) or -1) + for i = 0, 7 do + local fill = math.floor((args[i + 2] and tonumber(args[i + 2].value)) or 0) + local slot = self:findByName("slot" .. i, true) + if slot then + if i == cur then + slot.color = Color(0.27, 0.8, 0.4, 1) -- current (green) + elseif fill == 1 then + slot.color = Color(0.2, 0.4, 0.8, 1) -- filled (blue) + else + slot.color = Color(0.15, 0.15, 0.15, 1) -- empty (dark) + end + end + end +end diff --git a/touchosc/tests/test_layout.py b/touchosc/tests/test_layout.py index 40f3f3d..1811e8e 100644 --- a/touchosc/tests/test_layout.py +++ b/touchosc/tests/test_layout.py @@ -23,7 +23,7 @@ def _addr_list(el): # Root / pager # --------------------------------------------------------------------------- -def test_root_has_pager_with_three_pages(tmp_path): +def test_root_has_pager_with_four_pages(tmp_path): root = layout.build() out = tmp_path / "full.tosc" schema.write_tosc(root, str(out)) @@ -31,7 +31,7 @@ def test_root_has_pager_with_three_pages(tmp_path): pager = parsed.find(".//node[@type='PAGER']") assert pager is not None, "PAGER node not found" pages = pager.findall("children/node[@type='GROUP']") - assert len(pages) == 3, f"expected 3 pages, got {len(pages)}" + assert len(pages) == 4, f"expected 4 pages, got {len(pages)}" def test_sixteen_patterns(): @@ -322,3 +322,72 @@ def test_live_page_pad_groups_carry_armed_receive(): f"pad GROUP '{grp_name}' must have exactly 1 /armed/* receive msg " f"on the GROUP itself, got {len(armed_msgs)}" ) + + +# --------------------------------------------------------------------------- +# Page 4 — SECTIONS +# --------------------------------------------------------------------------- + +def test_sections_page_launch_buttons(): + """SECTIONS page: 8 /scene/launch messages with CONSTANT/INTEGER args 0..7.""" + page = layout.sections_page() + launch_args = [] + for osc_el in page.findall(".//messages/osc"): + path_val = osc_el.find("path/partial/value") + if path_val is not None and path_val.text == "/scene/launch": + for part in osc_el.findall("arguments/partial"): + typ = part.find("type") + conv = part.find("conversion") + val = part.find("value") + if typ is not None and conv is not None and val is not None: + launch_args.append((typ.text, conv.text, val.text)) + assert len(launch_args) == 8, f"expected 8 /scene/launch arg partials, got {len(launch_args)}" + for typ, conv, _ in launch_args: + assert typ == "CONSTANT", f"launch arg type must be CONSTANT, got {typ}" + assert conv == "INTEGER", f"launch arg conversion must be INTEGER, got {conv}" + vals = {v for _, _, v in launch_args} + assert vals == {"0", "1", "2", "3", "4", "5", "6", "7"}, ( + f"expected launch values 0..7, got {vals}" + ) + + +def test_sections_page_save_buttons(): + """SECTIONS page: 8 /scene/save messages with CONSTANT/INTEGER args 0..7.""" + page = layout.sections_page() + save_args = [] + for osc_el in page.findall(".//messages/osc"): + path_val = osc_el.find("path/partial/value") + if path_val is not None and path_val.text == "/scene/save": + for part in osc_el.findall("arguments/partial"): + typ = part.find("type") + conv = part.find("conversion") + val = part.find("value") + if typ is not None and conv is not None and val is not None: + save_args.append((typ.text, conv.text, val.text)) + assert len(save_args) == 8, f"expected 8 /scene/save arg partials, got {len(save_args)}" + for typ, conv, _ in save_args: + assert typ == "CONSTANT", f"save arg type must be CONSTANT, got {typ}" + assert conv == "INTEGER", f"save arg conversion must be INTEGER, got {conv}" + + +def test_sections_page_nav_buttons(): + """SECTIONS page: /scene/prev and /scene/next present.""" + page = layout.sections_page() + addrs = _addrs(page) + assert "/scene/prev" in addrs, "/scene/prev not found on SECTIONS page" + assert "/scene/next" in addrs, "/scene/next not found on SECTIONS page" + + +def test_sections_page_state_receive_on_group(): + """SECTIONS GROUP itself must carry /scene/state receive so sections.lua fires.""" + page = layout.sections_page() + # Direct messages/osc on the top-level GROUP (not descendants) + state_msgs = [ + osc_el for osc_el in page.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 == "/scene/state" + ] + assert len(state_msgs) == 1, ( + f"SECTIONS GROUP must have exactly 1 /scene/state receive msg, got {len(state_msgs)}" + )