Skip to content

  • Projects
  • Groups
  • Snippets
  • Help
  • This project
    • Loading...
  • Sign in / Register
T
Tidal
  • Overview
    • Overview
    • Details
    • Activity
    • Cycle Analytics
  • Repository
    • Repository
    • Files
    • Commits
    • Branches
    • Tags
    • Contributors
    • Graph
    • Compare
    • Charts
  • Issues 0
    • Issues 0
    • List
    • Board
    • Labels
    • Milestones
  • Merge Requests 0
    • Merge Requests 0
  • CI / CD
    • CI / CD
    • Pipelines
    • Jobs
    • Schedules
    • Charts
  • Wiki
    • Wiki
  • Snippets
    • Snippets
  • Members
    • Members
  • Collapse sidebar
  • Activity
  • Graph
  • Charts
  • Create a new issue
  • Jobs
  • Commits
  • Issue Boards
  • PLN
  • Tidal
  • Repository

Switch branch/tag
  • Tidal
  • tools
  • tests
  • test_lcxl3_display.py
Find file
BlameHistoryPermalink
  • PLN (Algolia)'s avatar
    fix(lcxl3): we were fighting for the wrong screen — every knob owns its own · 78ba7fca
    PLN, three rounds in: the OLED "still shows values". Move a fader and you get
    the firmware's bare 0-127; move a relative encoder and you get "0", because the
    wire value of a relative encoder IS 0-ish. "No info at all." Meanwhile our own
    overlay — track name, what the control does, a readout — was being re-summoned
    every 0.9 s under a 1.2 s firmware timeout, field-diffed, stamped only on real
    bring-ups. Three increasingly clever versions of the same losing move.
    
    The programmer's reference says why, in the display chapter's first list:
    
        "• 05h (5) - 24h (36): Temporary display for Analogue controls (same as CC
           indices, 05h (5) - 0Ch (12): Faders, 0Dh (13) - 24h (36): Encoders)
         • 35h (53): Permanent/Stationary display
         • 36h (54): Overlay/Temporary display"
    
    There are not two display targets. There are thirty-four. Every fader and every
    encoder owns a display target of its own, addressed by its own DAW-mode CC
    index, and THAT is the one the firmware auto-triggers when you touch the
    control — not 36h, which is where we had been writing all evening. Config bit 6
    is its on-switch and it defaults to on:
    
        "• Bit 6: Allow Launch Control XL 3 to generate temporary display
           automatically on Change (default: Set).
         • Bit 5: Allow Launch Control XL 3 to generate temporary display
           automatically on Touch (default: Set; this is the Shift + rotate)."
    
    So a fader sweep fired target 05h thirty times a second while we re-summoned
    36h once a second. Unwinnable by construction, and no amount of keep-alive
    tuning was ever going to change that. It also explains the one part of the
    symptom that never fit a timeout story — buttons were fine. Buttons live at
    25h-34h, outside the analogue range, own no per-control target, and so were
    never contested at all.
    
    And the fix is not to win the fight. Arrangement 4 is the firmware's default:
    
        "4 | 2 lines: Parameter Name and Numeric Parameter Value (default) | Yes | 1 | Name"
    
    One field, Name, and a value the firmware draws itself. The Name field was
    empty because nobody had ever written it. Write it, and the display we were
    trying to suppress becomes the display we wanted: it already tracks the value at
    wire speed and already times its own dismissal. So the driver now labels all 32
    analogue controls on their own targets at startup — "d4 # crushbus",
    "d5 # octerbus", "d1 level [ard]" — and stands down from the overlay for them.
    
    Faders and A1-A4 carry no ^NN in any .tidal, because they are Ardour-learned
    levels; their label comes from the grid's role table instead, with an explicit
    [ard] mark. Mid-set, "this fader is Ardour's, not Tidal's" is the most useful
    sixteen characters the panel can hold. Unmapped controls say "unmapped", because
    dark-means-unmapped is the surface's oldest rule and the screen must not
    contradict the LED beside it.
    
    Three controls keep our overlay: the DJF knobs. "HPF 3.4kHz !!" and the heart
    frames at the zero detent are prose, and arrangement 4's value field is a
    number. For exactly those three targets we clear bits 5+6 — which is the
    documented, per-control way to make the firmware stand down — and take the
    screen back uncontested. Restored on exit, since a next session finding three
    knobs that silently refuse to show a value would be a worse bug than this one.
    
    Along the way, three things the per-control screen made visible by being the
    first thing to render our labels at full width:
    
    - set_text filtered to `0x20 <= c <= 0x7E` and so dropped the four reassigned
      control codes its own docstring advertised. The language module's heart frames
      had been arriving as plain spaces this whole time.
    - parse_context said "an inline trailing comment IS the best label" and did not
      do it: the only `--` strip was anchored at the start of the body, where a
      trailing comment never is, so the Haskell always won. C8 of piment_bresilien
      read `d8 # n "23")) --` on a line ending in PLN's own `-- Raise COMEON!`.
      The comment is split off first now and preferred.
    - With no stage note, the body fallback was raw combinator soup. It now reads
      the sample name off the first quoted string and the effect off the first
      identifier — "break:18", "n 6" — which is what PLN asked the line to say
      ("sample name or effect like 'rose:4'"). Across live/: 158 syntax-noisy
      labels out of 22368 became 3.
    
    Verification I can actually do, given a write-only display and no readback:
    --show-oled dry-runs the whole label table offline, opening no port, and fails
    on any name over 16 characters; the new test pins the target numbers against the
    guide's table, the exact configure/set_text bytes, that a button raises instead
    of writing into the void, that the hearts survive the filter, and the three
    parse_context cases above. What no test can prove is a pixel. That needs eyes.
    
    Fallbacks kept in case the name field turns out not to render: --oled-overlay
    suppresses every native display and restores the uncontested-overlay behaviour
    across the whole surface, and --oled-native-all hands even the DJFs to the
    firmware, for the eye test that compares the two side by side.
    PLN (Algolia) authored Aug 29, 2026
    78ba7fca
test_lcxl3_display.py 7.6 KB
EditWeb IDE
×

Replace test_lcxl3_display.py

Attach a file by drag & drop or click to upload


Cancel
A new branch will be created in your fork and a new merge request will be started.