From cfe0016184a4c4e4a992c2faf72570787b7b1ae4 Mon Sep 17 00:00:00 2001 From: "prompt.ac/@jeffrey" Date: Thu, 24 Sep 2026 17:31:21 -0700 Subject: [PATCH] ac-native: per-card SOF volume baseline + config.json "volume" On SOF cards with no Master mixer the software gain defaulted to 180% (3.2x linear), tuned for the Jasper Lake sof-rt5682 + MAX98360A pipeline and its topology headroom. The Gemini Lake sof-glk-da7219 + MAX98357A path (Lenovo 500e) runs at full scale, so 180% was painfully loud and clipped into crunch. Baseline is now per card id: glk/bxt da7219 boards start at 80%, everything else keeps 180%. config.json "volume" (0-400, percent) overrides the baseline so a stick can be tuned without a rebuild: ac-inscribe --volume writes it into the inscription and flash-mac.sh carries it into config.json. ac-native parses it before audio_init via audio_set_default_system_volume(); a live audio_set_system_volume() setter comes along for pieces. --- fedac/native/ac-inscribe | 3 ++ fedac/native/scripts/flash-mac.sh | 1 + fedac/native/scripts/inscribe-lib.sh | 5 +-- fedac/native/src/ac-native.c | 26 ++++++++++++++++ fedac/native/src/audio.c | 46 ++++++++++++++++++++++++++-- fedac/native/src/audio.h | 4 +++ 6 files changed, 80 insertions(+), 5 deletions(-) diff --git a/fedac/native/ac-inscribe b/fedac/native/ac-inscribe index 4f5545b10..818e1d948 100755 --- a/fedac/native/ac-inscribe +++ b/fedac/native/ac-inscribe @@ -17,6 +17,7 @@ # ac-inscribe --mood # override boot splash subtitle (else uses the handle's AC mood) # ac-inscribe --city # preset first-boot greeting city until the device geolocates (e.g. "Ridgewood") # ac-inscribe --mono # boot with the system-wide mono audio fold on (config "mono": true) +# ac-inscribe --volume # baseline output volume 0-400 (config "volume"); e.g. 60 for a loud speaker path # # Exit codes: # 0 inscription written and valid @@ -56,6 +57,7 @@ CITY_OVERRIDE="" CITY_OVERRIDE_SET=0 ACI_CITY="" # config "city"; only set via --city (no account fetch), so default empty under `set -u` ACI_MONO="" # config "mono": "1" via --mono → device boots with the audio fold on +ACI_VOLUME="" # config "volume": percent via --volume → baseline software gain at boot while [ $# -gt 0 ]; do case "$1" in @@ -68,6 +70,7 @@ while [ $# -gt 0 ]; do --mood) MOOD_OVERRIDE="$2"; MOOD_OVERRIDE_SET=1; shift ;; --city) CITY_OVERRIDE="$2"; CITY_OVERRIDE_SET=1; shift ;; --mono) ACI_MONO=1 ;; + --volume) ACI_VOLUME="$2"; shift ;; --help|-h) sed -n '2,28p' "$0" | sed 's/^# *//' exit 0 diff --git a/fedac/native/scripts/flash-mac.sh b/fedac/native/scripts/flash-mac.sh index b82701ea0..beeb0f37c 100755 --- a/fedac/native/scripts/flash-mac.sh +++ b/fedac/native/scripts/flash-mac.sh @@ -574,6 +574,7 @@ write_device_config() { # $1=dest $2=udp(1=include udpMidiBroadcast) if (Array.isArray(c.colors) && c.colors.length) cfg.colors = c.colors; if (c.mood) cfg.mood = c.mood; if (typeof c.mono === "boolean") cfg.mono = c.mono; + if (Number.isFinite(c.volume)) cfg.volume = Math.max(0, Math.min(400, Math.round(c.volume))); if (["left", "right", "both"].includes(c.monoOutput)) cfg.monoOutput = c.monoOutput; } catch (e) { /* no inscription (anon/legacy) — base fields only */ } fs.writeFileSync(dest, JSON.stringify(cfg) + "\n"); diff --git a/fedac/native/scripts/inscribe-lib.sh b/fedac/native/scripts/inscribe-lib.sh index 0e09ef8c8..f12e18d39 100644 --- a/fedac/native/scripts/inscribe-lib.sh +++ b/fedac/native/scripts/inscribe-lib.sh @@ -256,7 +256,7 @@ aci_collect_local_claude() { # and ACI_MOOD (plain string), ACI_CITY, ACI_MONO ("1" = boot in mono). aci_build_usb_config_json() { node -e " - const [handle, sub, email, token, claudeToken, githubPat, claudeCreds, claudeState, colorsJson, mood, city, mono] = process.argv.slice(1); + const [handle, sub, email, token, claudeToken, githubPat, claudeCreds, claudeState, colorsJson, mood, city, mono, volume] = process.argv.slice(1); const cfg = { handle, sub, email, token }; if (claudeToken) cfg.claudeToken = claudeToken; if (githubPat) cfg.githubPat = githubPat; @@ -271,11 +271,12 @@ aci_build_usb_config_json() { if (mood) cfg.mood = mood; if (city) cfg.city = city; if (mono === '1') cfg.mono = true; // boot with the audio fold on (config.json \"mono\") + if (volume !== '' && Number.isFinite(Number(volume))) cfg.volume = Math.max(0, Math.min(400, Math.round(Number(volume)))); // baseline output gain % process.stdout.write(JSON.stringify(cfg)); " "${ACI_HANDLE:-}" "${ACI_SUB:-}" "${ACI_EMAIL:-}" "${ACI_ACCESS_TOKEN:-}" \ "${ACI_CLAUDE_TOKEN:-}" "${ACI_GITHUB_PAT:-}" \ "${ACI_CLAUDE_CREDS:-}" "${ACI_CLAUDE_STATE:-}" \ - "${ACI_HANDLE_COLORS_JSON:-}" "${ACI_MOOD:-}" "${ACI_CITY:-}" "${ACI_MONO:-}" + "${ACI_HANDLE_COLORS_JSON:-}" "${ACI_MOOD:-}" "${ACI_CITY:-}" "${ACI_MONO:-}" "${ACI_VOLUME:-}" } # aci_build_inscription_json diff --git a/fedac/native/src/ac-native.c b/fedac/native/src/ac-native.c index 81eae0275..bb5b0a2c6 100644 --- a/fedac/native/src/ac-native.c +++ b/fedac/native/src/ac-native.c @@ -740,6 +740,7 @@ extern int drm_acquire_master(void *display); // Boot title — defaults to "notepat", overridden by config.json handle int wifi_disabled = 0; // set from config.json "wifi":false (extern'd in js-bindings.c) int config_mono = 0; // set from config.json "mono":true — applied once audio is up +int config_volume = -1; // config.json "volume" (percent baseline); -1 = card default static char boot_title[80] = "notepat"; static ACColor boot_title_colors[80]; static int boot_title_colors_len = 0; @@ -787,6 +788,21 @@ static int parse_config_string(const char *json, const char *key, char *out, int return 1; } +// Parse an integer value for `key` ("\"volume\"") out of a flat JSON blob. +static int parse_config_int(const char *json, const char *key, int *out) { + const char *p = strstr(json, key); + if (!p) return 0; + p = strchr(p + strlen(key), ':'); + if (!p) return 0; + p++; + while (*p == ' ' || *p == '\t') p++; + char *end = NULL; + long v = strtol(p, &end, 10); + if (end == p) return 0; + *out = (int)v; + return 1; +} + static int parse_config_bool(const char *json, const char *key, int *out) { if (!json || !key || !out) return 0; const char *kp = strstr(json, key); @@ -919,6 +935,16 @@ static void load_boot_visual_config(void) { ac_log("[config] audio: %s\n", config_mono ? "mono" : "stereo"); } + // Read baseline output volume (percent). Baked per stick by + // `ac-inscribe --volume`; lets a loud speaker path be tamed without a + // rebuild. Applied as the software gain default before audio_init. + int volume_val = -1; + if (parse_config_int(json, "\"volume\"", &volume_val) && volume_val >= 0 && volume_val <= 400) { + config_volume = volume_val; + audio_set_default_system_volume(config_volume); + ac_log("[config] audio volume: %d%%\n", config_volume); + } + // Bake Claude/GitHub tokens early so boot-fade badge check (access()) // succeeds while the fade is animating. The duplicate in main() runs // after the fade and was leaving the badges invisible. diff --git a/fedac/native/src/audio.c b/fedac/native/src/audio.c index 9ad5c7ea5..d9b990b7c 100644 --- a/fedac/native/src/audio.c +++ b/fedac/native/src/audio.c @@ -22,6 +22,9 @@ extern void ac_log(const char *fmt, ...); // Forward declarations static int read_system_volume_card(int card); +// config.json "volume" (percent) — set before audio_init by ac-native so the +// baseline software gain is per-stick, not per-build. -1 = not set. +static int default_system_volume_override = -1; // qsort comparator for AC_LATENCY_BENCH percentile computation. static int bench_cmp_long(const void *a, const void *b) { @@ -3389,9 +3392,34 @@ ACAudio *audio_init(void) { // pushing soft_clip into audible distortion. Volume keys still go // up to 400% for very quiet hardware. int hw_vol = read_system_volume_card(card_idx); - audio->system_volume = (hw_vol >= 0) ? hw_vol : 180; - fprintf(stderr, "[audio] System volume: %d%% (hw=%d)\n", - audio->system_volume, hw_vol); + int sof_default = 180; + { + // Per-card SOF baselines (no Master mixer, software gain only). + // 180% (3.2x linear) suits the Jasper Lake sof-rt5682 + MAX98360A + // pipeline, whose topology leaves ~6 dB of headroom. The Gemini + // Lake sof-glk-da7219 + MAX98357A path (Lenovo 500e) runs at full + // scale: 180% there was painfully loud and clipped into crunch + // (2026-09-24). config.json "volume" overrides either. + char cid[32] = ""; + char cid_path[64]; + snprintf(cid_path, sizeof(cid_path), "/proc/asound/card%d/id", card_idx); + FILE *cf = fopen(cid_path, "r"); + if (cf) { + if (fgets(cid, sizeof(cid), cf)) { char *nl = strchr(cid, '\n'); if (nl) *nl = 0; } + fclose(cf); + } + if (strstr(cid, "glkda7219") || strstr(cid, "bxtda7219")) sof_default = 80; + if (cid[0]) fprintf(stderr, "[audio] Card id: %s (SOF baseline %d%%)\n", cid, sof_default); + } + if (default_system_volume_override >= 0) { + audio->system_volume = default_system_volume_override; + fprintf(stderr, "[audio] System volume: %d%% (config.json volume; hw=%d)\n", + audio->system_volume, hw_vol); + } else { + audio->system_volume = (hw_vol >= 0) ? hw_vol : sof_default; + fprintf(stderr, "[audio] System volume: %d%% (hw=%d)\n", + audio->system_volume, hw_vol); + } // HDMI audio disabled — opening HDMI PCM streams on the same HDA controller // can exhaust controller streams and cause EIO on capture. @@ -3876,6 +3904,18 @@ void audio_set_master_volume(ACAudio *audio, float value) { audio->target_master_volume = value; } +void audio_set_default_system_volume(int pct) { + default_system_volume_override = (pct < 0) ? -1 : (pct > 400 ? 400 : pct); +} + +void audio_set_system_volume(ACAudio *audio, int pct) { + if (!audio) return; + if (pct < 0) pct = 0; + if (pct > 400) pct = 400; + audio->system_volume = pct; + ac_log("[audio] System volume set to %d%%\n", pct); +} + void audio_set_mono(ACAudio *audio, int enabled) { if (!audio) return; audio->mono = enabled ? 1 : 0; diff --git a/fedac/native/src/audio.h b/fedac/native/src/audio.h index 9437b4ac1..acf77de8d 100644 --- a/fedac/native/src/audio.h +++ b/fedac/native/src/audio.h @@ -520,6 +520,10 @@ void audio_set_fx_mix(ACAudio *audio, float mix); void audio_set_master_volume(ACAudio *audio, float value); // Mono fold on/off (see ACAudio.mono). void audio_set_mono(ACAudio *audio, int enabled); +// Baseline output gain in percent (0..400). The default form must be called +// before audio_init (config.json "volume"); the other adjusts a live engine. +void audio_set_default_system_volume(int pct); +void audio_set_system_volume(ACAudio *audio, int pct); void audio_set_drive_mix(ACAudio *audio, float value); void audio_set_wobble_mix(ACAudio *audio, float value); -- 2.51.2