Add a brightness-only sink: keep the grabber's colour, pulse with sound

Every existing HyperHDR route replaces whatever else is on the LEDs:
routes 1/2 hand HyperHDR's own audio effect a device to read, route 3
sends a synthetic spectrum image, and both take over via HyperHDR's
priority system. For a setup that already has a real colour source
(a screen grabber, a USB capture card) feeding an ambilight-style LED
run, none of that is what's wanted -- the colour should stay put and
only brightness should react.

Read HyperHDR's own source (sources/api/JSONRPC_schema/schema-adjustment.json)
rather than guess: "adjustment" is a post-processing command with a
scaleOutput parameter (0-2.0) that applies regardless of which
priority is currently active. Confirmed the wire format too --
sources/jsonserver/JsonClientConnection.cpp frames it as plain
newline-delimited JSON over TCP (default port 19444), nothing like the
length-prefixed Flatbuffers protocol the visualiser sink speaks, and
with no handshake or registration needed before the first write.

sink_hyperhdr_adjust.c sends only that: no image, no priority, so it
never competes with an existing grabber. Non-blocking connect with the
same poll()+SO_ERROR pattern net/hyperion.c already uses, reconnects
every 5s, rate-limited to 20 Hz (a HyperHDR command every audio block
would be pointless flooding), and resets scaleOutput to 1.0 on close
rather than leaving the LEDs stuck at whatever it last sent. Cross-
compiles clean under -Wall -Wextra on the real webOS toolchain.

Wired through the same path every other sink follows: registered in
sink.c/sink.h, defaults in config.c, fields in frontend/js/app.js
(SINK_FIELDS/SINK_HELP), mock.js and ui_smoke.js updated for the new
sink card. Bumped to 1.0.3.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Rene Kievits
2026-08-26 14:36:31 +02:00
co-authored by Claude Opus 5
parent f0f68a1aa7
commit aae5a33283
15 changed files with 402 additions and 13 deletions
+34
View File
@@ -383,6 +383,33 @@
},
],
hyperhdrAdjust: [
{
path: 'hyperhdrAdjust.host', label: 'HyperHDR address', type: 'text', wide: true,
placeholder: '192.168.1.50',
},
{
path: 'hyperhdrAdjust.port', label: 'JSON-RPC port', type: 'number',
hint: 'HyperHDR\'s classic control port, 19444 by default. Not the '
+ 'web UI port (8090) or the Flatbuffers port (19400).',
},
{
path: 'hyperhdrAdjust.level', label: 'Follows', type: 'choice',
options: [
{ value: 'rms', label: 'Average level (steadier)' },
{ value: 'peak', label: 'Peak level (punchier)' },
],
},
{
path: 'hyperhdrAdjust.minScale', label: 'Minimum brightness', type: 'number',
hint: '1.0 is HyperHDR\'s normal brightness. Below that dims during quiet parts.',
},
{
path: 'hyperhdrAdjust.maxScale', label: 'Maximum brightness', type: 'number',
hint: 'Above 1.0 boosts past normal on loud peaks. HyperHDR accepts up to 2.0.',
},
],
udp: [
{
path: 'udp.host', label: 'Destination', type: 'text', wide: true,
@@ -414,6 +441,10 @@
hyperhdrViz: 'No host setup at all: the TV does the analysis and sends '
+ 'finished images over the Flatbuffers port. Use it when you cannot add '
+ 'a sound device on the HyperHDR machine.',
hyperhdrAdjust: 'For an existing ambilight/grabber setup: leaves colour '
+ 'entirely to whatever HyperHDR is already showing, and only turns its '
+ 'overall brightness up and down with the sound. Sends no image, so it '
+ 'never competes for priority with a grabber.',
udp: 'Raw interleaved S16LE, no header, no framing. Lowest latency and no '
+ 'connection to lose.',
tcp: 'Raw interleaved S16LE over a stream. Reliable, at the cost of '
@@ -662,6 +693,9 @@
if (s.framesSent !== undefined) {
bits.push(s.framesSent.toLocaleString() + ' frames');
}
if (s.updatesSent !== undefined) {
bits.push(s.updatesSent.toLocaleString() + ' updates');
}
if (s.connected !== undefined) {
bits.push(s.connected ? 'connected' : 'not connected');
}