feat: audio - add concurrent mixing, output switching, and device-aware UI

This commit is contained in:
2026-07-29 20:03:35 +01:00
parent d99dab45e5
commit 86de3400a9
25 changed files with 688 additions and 209 deletions
+18 -1
View File
@@ -390,7 +390,7 @@ void gui::desktop_handle_mouse(DesktopState* ds) {
int popup_x = ds->vol_icon_rect.x + ds->vol_icon_rect.w - 200;
int popup_y = PANEL_HEIGHT + 2;
if (popup_x < 4) popup_x = 4;
Rect vol_rect = {popup_x, popup_y, 200, 120};
Rect vol_rect = {popup_x, popup_y, 200, 202};
// Handle drag continuity
if (ds->vol_dragging) {
@@ -482,6 +482,23 @@ void gui::desktop_handle_mouse(DesktopState* ds) {
return;
}
}
// Output device radio rows.
Rect hda_r = {popup_x + 12, popup_y + 137, 200 - 24, 25};
Rect bt_r = {hda_r.x, hda_r.y + hda_r.h, hda_r.w, hda_r.h};
if (hda_r.contains(mx, my) &&
ds->vol_output != montauk::abi::AUDIO_OUTPUT_HDA) {
if (montauk::audio_set_output(montauk::abi::AUDIO_OUTPUT_HDA) == 0)
ds->vol_output = montauk::abi::AUDIO_OUTPUT_HDA;
return;
}
if (bt_r.contains(mx, my) && ds->vol_bt_status >= 2 &&
ds->vol_output != montauk::abi::AUDIO_OUTPUT_BLUETOOTH) {
if (montauk::audio_set_output(
montauk::abi::AUDIO_OUTPUT_BLUETOOTH) == 0)
ds->vol_output = montauk::abi::AUDIO_OUTPUT_BLUETOOTH;
return;
}
return; // click inside popup but not on any control
} else if (!ds->vol_icon_rect.contains(mx, my)) {
ds->vol_popup_open = false;
+12
View File
@@ -401,6 +401,8 @@ void gui::desktop_init(DesktopState* ds) {
ds->vol_dragging = false;
ds->vol_last_poll = montauk::get_milliseconds();
ds->vol_serial = montauk::audio_wait(0, 0);
ds->vol_output = montauk::audio_get_output(-1);
ds->vol_bt_status = montauk::audio_bt_status(-1);
ds->closing_ext_count = 0;
@@ -491,6 +493,8 @@ static bool desktop_refresh_panel_state(DesktopState* ds, uint64_t now) {
ds->vol_serial = serial;
int v = montauk::audio_get_master_volume();
bool muted = montauk::audio_get_master_mute() == 1;
int output = montauk::audio_get_output(-1);
int btStatus = montauk::audio_bt_status(-1);
if (v >= 0 && v != ds->vol_level) {
ds->vol_level = v;
changed = true;
@@ -499,6 +503,14 @@ static bool desktop_refresh_panel_state(DesktopState* ds, uint64_t now) {
ds->vol_muted = muted;
changed = true;
}
if (output >= 0 && output != ds->vol_output) {
ds->vol_output = output;
changed = true;
}
if (btStatus != ds->vol_bt_status) {
ds->vol_bt_status = btStatus;
changed = true;
}
}
ds->vol_last_poll = now;
}
+52 -1
View File
@@ -125,6 +125,17 @@ void gui::desktop_draw_panel(DesktopState* ds) {
tinted[p] = ((uint32_t)a << 24) | 0x00CC3333;
}
fb.blit_alpha(vol_icon_x, vol_icon_y, 16, 16, tinted);
} else if (ds->vol_output == montauk::abi::AUDIO_OUTPUT_BLUETOOTH) {
// The speaker glyph remains recognizable while the accent tint
// communicates that audio is routed wirelessly.
uint32_t* src = ds->icon_volume.pixels;
uint32_t tinted[256];
uint32_t rgb = ds->settings.accent_color.to_pixel() & 0x00FFFFFF;
for (int p = 0; p < 16 * 16; p++) {
uint8_t a = (src[p] >> 24) & 0xFF;
tinted[p] = ((uint32_t)a << 24) | rgb;
}
fb.blit_alpha(vol_icon_x, vol_icon_y, 16, 16, tinted);
} else {
fb.blit_alpha(vol_icon_x, vol_icon_y, ds->icon_volume.width, ds->icon_volume.height, ds->icon_volume.pixels);
}
@@ -337,7 +348,7 @@ void desktop_draw_net_popup(DesktopState* ds) {
// ============================================================================
static constexpr int VOL_POPUP_W = 200;
static constexpr int VOL_POPUP_H = 120;
static constexpr int VOL_POPUP_H = 202;
static constexpr int VOL_SLIDER_X = 16;
static constexpr int VOL_SLIDER_W = VOL_POPUP_W - 32;
static constexpr int VOL_SLIDER_H = 8;
@@ -455,4 +466,44 @@ void desktop_draw_vol_popup(DesktopState* ds) {
fill_rounded_rect(fb, bx, btn_y, mute_w, btn_h, btn_rad, mute_bg);
tw = text_width("Mute");
draw_text(fb, bx + (mute_w - tw) / 2, btn_y + (btn_h - system_font_height()) / 2, "Mute", mute_fg);
// Output is a persistent selection, so present it as a conventional radio
// group rather than a second bank of action buttons.
int output_label_y = popup_y + 116;
draw_text(fb, popup_x + 16, output_label_y, "Output",
Color::from_rgb(0x77, 0x77, 0x77));
auto draw_device_row = [&](const Rect& row, const char* label,
bool selected, bool enabled) {
bool hovered = enabled && row.contains(ds->mouse.x, ds->mouse.y);
if (hovered)
fill_rounded_rect(fb, row.x, row.y, row.w, row.h, 5,
Color::from_rgb(0xF0, 0xF3, 0xF6));
int radio_size = 14;
int rx = row.x + 4;
int ry = row.y + (row.h - radio_size) / 2;
fill_rounded_rect(fb, rx, ry, radio_size, radio_size,
radio_size / 2, Color::from_rgb(0xAA, 0xAA, 0xAA));
fill_rounded_rect(fb, rx + 1, ry + 1, radio_size - 2,
radio_size - 2, (radio_size - 2) / 2, colors::WHITE);
if (selected)
fill_rounded_rect(fb, rx + 4, ry + 4, radio_size - 8,
radio_size - 8, (radio_size - 8) / 2,
ds->settings.accent_color);
Color fg = enabled ? colors::TEXT_COLOR
: Color::from_rgb(0x99, 0x99, 0x99);
draw_text(fb, rx + radio_size + 8,
row.y + (row.h - system_font_height()) / 2, label, fg);
};
Rect hda_r = {popup_x + 12, popup_y + 137, VOL_POPUP_W - 24, 25};
Rect bt_r = {hda_r.x, hda_r.y + hda_r.h, hda_r.w, hda_r.h};
bool bt_ready = ds->vol_bt_status >= 2;
draw_device_row(hda_r, "Built-in Speakers",
ds->vol_output == montauk::abi::AUDIO_OUTPUT_HDA, true);
draw_device_row(bt_r, bt_ready ? "Bluetooth" : "Bluetooth (offline)",
ds->vol_output == montauk::abi::AUDIO_OUTPUT_BLUETOOTH,
bt_ready);
}