Fix hyperhdrAdjust: use "brightness", not "scaleOutput"

Tested against a real HyperHDR instance rather than trusting the
schema further. scaleOutput (from the current schema-adjustment.json)
produced no visible change and no trace in serverinfo's echoed-back
adjustment state. brightness (0-100, absent from that same schema)
round-tripped correctly through serverinfo and visibly dimmed real
LEDs -- confirmed live, with piccap as the sole colour source and
only this sink's JSON-RPC calls changing anything.

A schema documents what a command accepts; it does not guarantee
what a given build actually does with each field, and this is a
mismatch between HyperHDR's current dev-branch schema and whatever
build the target instance is actually running. Switched the sink to
brightness (int 0-100) throughout: wire format, config defaults
(minBrightness/maxBrightness, 20-100), status fields, and the reset
sent on close. Renamed the frontend fields and mock to match.
Cross-compiles clean. Bumped to 1.0.4.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Rene Kievits
2026-08-26 14:59:51 +02:00
co-authored by Claude Opus 5
parent aae5a33283
commit e9f6c87d27
9 changed files with 80 additions and 53 deletions
+2 -2
View File
@@ -51,8 +51,8 @@ static const char* DEFAULTS_JSON =
" \"hyperhdrAdjust\": {"
" \"host\": \"\","
" \"port\": 19444,"
" \"minScale\": 0.3,"
" \"maxScale\": 1.3,"
" \"minBrightness\": 20,"
" \"maxBrightness\": 100,"
" \"level\": \"rms\""
" },"
" \"udp\": { \"host\": \"\", \"port\": 4010, \"multicastTtl\": 4 },"
+35 -23
View File
@@ -1,12 +1,22 @@
// Global brightness modulation via HyperHDR's own JSON-RPC "adjustment"
// command, so a real picture source (HyperHDR's screen grabber, USB capture
// card, whatever is already driving the LEDs) keeps deciding colour, and
// only the overall brightness reacts to sound. Unlike every other sink here,
// this one deliberately sends no picture at all: send one would mean
// competing for priority against the grabber, replacing its colour outright
// instead of layering on top of it. "adjustment" is a post-processing stage
// that applies regardless of which priority is currently active, which is
// exactly the layering this needs.
// command, so a real picture source (HyperHDR's screen grabber, a webOS
// capture app like piccap, whatever is already driving the LEDs) keeps
// deciding colour, and only the overall brightness reacts to sound. Unlike
// every other sink here, this one deliberately sends no picture at all:
// sending one would mean competing for priority against that source,
// replacing its colour outright instead of layering on top of it.
// "adjustment" is a post-processing stage that applies regardless of which
// priority is currently active, which is exactly the layering this needs.
//
// The field is "brightness", an integer 0-100 -- confirmed against a real
// HyperHDR instance (serverinfo echoes it back, and the LEDs visibly
// responded). The "scaleOutput" float (0-2.0) in HyperHDR's current
// schema-adjustment.json looked like the obvious candidate and is what an
// earlier version of this file sent, but it produced no visible or
// server-reported effect on that same instance -- API docs describe the
// schema; they do not guarantee what a given build actually does with it.
// If a future HyperHDR drops "brightness" in favour of "scaleOutput", this
// will need re-verifying the same way, not just re-reading the schema.
//
// Plain newline-delimited JSON over TCP -- HyperHDR's classic control port,
// default 19444 -- a world simpler than the Flatbuffers image protocol the
@@ -19,6 +29,7 @@
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <math.h>
#include <netdb.h>
#include <netinet/in.h>
#include <poll.h>
@@ -38,8 +49,8 @@ typedef struct {
char host[128];
int port;
float min_scale;
float max_scale;
int min_brightness; // 0-100
int max_brightness; // 0-100
bool use_rms; // rms is steadier than peak, which reacts to single transients
int fd; // -1 when not connected or still connecting
@@ -154,11 +165,12 @@ static void adjust_write(sink_t* s, const int16_t* pcm, int frames, const dsp_le
level = 0;
if (level > 1)
level = 1;
float scale = p->min_scale + level * (p->max_scale - p->min_scale);
int brightness = p->min_brightness
+ (int)lroundf(level * (float)(p->max_brightness - p->min_brightness));
char msg[128];
int n = snprintf(msg, sizeof(msg),
"{\"command\":\"adjustment\",\"adjustment\":{\"scaleOutput\":%.3f}}\n", (double)scale);
"{\"command\":\"adjustment\",\"adjustment\":{\"brightness\":%d}}\n", brightness);
if (n <= 0 || (size_t)n >= sizeof(msg))
return;
@@ -183,8 +195,8 @@ static void adjust_status(sink_t* s, json_writer_t* w)
jw_str(w, "target", p->host);
jw_int(w, "port", p->port);
jw_bool(w, "connected", p->connected);
jw_num(w, "minScale", p->min_scale);
jw_num(w, "maxScale", p->max_scale);
jw_int(w, "minBrightness", p->min_brightness);
jw_int(w, "maxBrightness", p->max_brightness);
jw_int(w, "updatesSent", (long long)p->updates_sent);
jw_int(w, "connectFailures", (long long)p->connect_failures);
if (p->last_error[0])
@@ -200,7 +212,7 @@ static void adjust_close(sink_t* s)
// Best effort: hand brightness back to normal rather than leaving
// the LEDs stuck at whatever scale was last sent.
if (p->connected) {
static const char reset[] = "{\"command\":\"adjustment\",\"adjustment\":{\"scaleOutput\":1.0}}\n";
static const char reset[] = "{\"command\":\"adjustment\",\"adjustment\":{\"brightness\":100}}\n";
send(p->fd, reset, sizeof(reset) - 1, MSG_NOSIGNAL);
}
adjust_disconnect(p);
@@ -214,8 +226,8 @@ static sink_t* adjust_open(const json_value_t* cfg, const audio_format_t* fmt, c
const json_value_t* sc = json_get(cfg, "hyperhdrAdjust");
const char* host = json_str(sc, "host", NULL);
int port = json_int(sc, "port", 19444);
float min_scale = (float)json_num(sc, "minScale", 0.3);
float max_scale = (float)json_num(sc, "maxScale", 1.3);
int min_brightness = json_int(sc, "minBrightness", 20);
int max_brightness = json_int(sc, "maxBrightness", 100);
const char* level_source = json_str(sc, "level", "rms");
if (!host || !*host) {
@@ -226,8 +238,8 @@ static sink_t* adjust_open(const json_value_t* cfg, const audio_format_t* fmt, c
snprintf(err, errlen, "invalid HyperHDR JSON port %d", port);
return NULL;
}
if (max_scale <= min_scale) {
snprintf(err, errlen, "maxScale must be greater than minScale");
if (min_brightness < 0 || max_brightness > 100 || max_brightness <= min_brightness) {
snprintf(err, errlen, "brightness range must be 0-100 with max greater than min");
return NULL;
}
@@ -261,8 +273,8 @@ static sink_t* adjust_open(const json_value_t* cfg, const audio_format_t* fmt, c
snprintf(p->host, sizeof(p->host), "%s", host);
p->port = port;
p->min_scale = min_scale;
p->max_scale = max_scale;
p->min_brightness = min_brightness;
p->max_brightness = max_brightness;
p->use_rms = strcmp(level_source, "peak") != 0;
s->driver = &sink_driver_hyperhdr_adjust;
@@ -272,8 +284,8 @@ static sink_t* adjust_open(const json_value_t* cfg, const audio_format_t* fmt, c
s->status = adjust_status;
s->close = adjust_close;
INFO("HyperHDR adjustment sink: %s:%d, scaleOutput %.2f..%.2f from %s", host, port,
min_scale, max_scale, p->use_rms ? "rms" : "peak");
INFO("HyperHDR adjustment sink: %s:%d, brightness %d..%d from %s", host, port,
min_brightness, max_brightness, p->use_rms ? "rms" : "peak");
return s;
}