diff --git a/workspace/desktop/libmsettings/msettings.h b/workspace/desktop/libmsettings/msettings.h index 0b302d08f..54c65888f 100644 --- a/workspace/desktop/libmsettings/msettings.h +++ b/workspace/desktop/libmsettings/msettings.h @@ -47,7 +47,7 @@ void SetJack(int value); // 0-1 #define AUDIO_SINK_DEFAULT 0 // use system default, usually speaker (or jack if plugged in) #define AUDIO_SINK_BLUETOOTH 1 // software control via bluealsa, not a separate card -#define AUDIO_SINK_USBDAC 2 // assumes being exposed as card 1 to alsa +#define AUDIO_SINK_USBDAC 2 // USB DAC card, monitored by audiomon int GetAudioSink(void); void SetAudioSink(int value); diff --git a/workspace/h700/libmsettings/msettings.c b/workspace/h700/libmsettings/msettings.c index eea421100..d3069a7df 100644 --- a/workspace/h700/libmsettings/msettings.c +++ b/workspace/h700/libmsettings/msettings.c @@ -787,6 +787,33 @@ static int get_a2dp_simple_control_name(char *buf, size_t buflen) { } static int get_usbc_card_num() { + // first choice - .asoundrc maintained by audiomon (card from ctl.!default section) + // second choice - .asoundrc maintained by audiomon (any card) + const char *userdata = getenv("USERDATA_PATH"); + if (userdata) { + char path[512]; + snprintf(path, sizeof(path), "%s/.asoundrc", userdata); + FILE *rc = fopen(path, "r"); + if (rc) { + char line[256]; + int in_ctl = 0, ctl_card = -1, any_card = -1, card_num; + while (fgets(line, sizeof(line), rc)) { + const char *p = line; + while (*p == ' ' || *p == '\t') p++; + if (strncmp(p, "ctl.", 4) == 0) in_ctl = 1; + else if (strncmp(p, "pcm.", 4) == 0) in_ctl = 0; + else if (strncmp(p, "card ", 5) == 0 && sscanf(p + 5, "%d", &card_num) == 1) { + if (in_ctl && ctl_card < 0) ctl_card = card_num; + if (any_card < 0) any_card = card_num; + } + } + fclose(rc); + if (ctl_card >= 0) return ctl_card; + if (any_card >= 0) return any_card; + } + } + + // fallback - first non-built-in card FILE *fp = popen("cat /proc/asound/cards", "r"); if (!fp) return -1; @@ -824,6 +851,38 @@ static int get_audiocodec_card_num() { return -1; } +// The rank of a mixer control of a USB audio device as its output volume, or -1 for a +// control that is not the output: capture, the microphone, and an input played into +// the output (sidetone, monitor, loopback, bypass, line in) +static int get_dac_control_playback_priority(const char* control_name) { + int capture_control = strstr(control_name, "Capture") + || strstr(control_name, "Mic"); + + int monitor_control = strstr(control_name, "Sidetone") + || strstr(control_name, "Monitor") + || strstr(control_name, "Loopback") + || strstr(control_name, "Line Playback") + || strstr(control_name, " In ") + || strstr(control_name, "Bypass"); + + if (capture_control || monitor_control) return -1; + + if (strstr(control_name, "Master")) return 3; + + if (strstr(control_name, "Headphone") + || strstr(control_name, "Headset") + || strstr(control_name, "Speaker") + || strstr(control_name, "Line Out") + || strstr(control_name, "Digital Out") + || strstr(control_name, "IEC958")) return 2; + + if (strstr(control_name, "PCM")) return 1; + + if (strstr(control_name, "Playback")) return 0; + + return -1; +} + void SetRawVolume(int val) { // in: 0-100 if (GetAudioSink() == AUDIO_SINK_BLUETOOTH) { // bluealsa is a mixer plugin, not exposed as a separate card @@ -843,7 +902,6 @@ void SetRawVolume(int val) { // in: 0-100 } } else if (GetAudioSink() == AUDIO_SINK_USBDAC) { - // USB DAC path: grab the first card that is not called "audiocodec" int card_num = get_usbc_card_num(); if(card_num < 0) { printf("Failed to find USB audio card\n"); fflush(stdout); @@ -856,24 +914,47 @@ void SetRawVolume(int val) { // in: 0-100 return; } - const unsigned int num_controls = mixer_get_num_ctls(mixer); - for (unsigned int i = 0; i < num_controls; i++) { - struct mixer_ctl *ctl = mixer_get_ctl(mixer, i); - const char *name = mixer_ctl_get_name(ctl); - if (!name) continue; - - if ((strstr(name, "PCM") || strstr(name, "Playback")) && (strstr(name, "Volume") || strstr(name, "volume"))) { - if (mixer_ctl_get_type(ctl) == MIXER_CTL_TYPE_INT) { - int min = mixer_ctl_get_range_min(ctl); - int max = mixer_ctl_get_range_max(ctl); - int volume = min + (val * (max - min)) / 100; - unsigned int num_values = mixer_ctl_get_num_values(ctl); - for (unsigned int i = 0; i < num_values; i++) - mixer_ctl_set_value(ctl, i, volume); - } - break; - } - } + const unsigned int num_controls = mixer_get_num_ctls(mixer); + + // find the best rank among the volume controls (-1 means not found) + int best_rank = -1; + for (unsigned int i = 0; i < num_controls; i++) { + struct mixer_ctl *ctl = mixer_get_ctl(mixer, i); + const char *name = mixer_ctl_get_name(ctl); + if (!name) continue; + if (!strstr(name, "Volume") && !strstr(name, "volume")) continue; + + int rank = get_dac_control_playback_priority(name); + if (rank > best_rank) best_rank = rank; + } + + // drive each volume and switch of that rank; the other controls keep their level + for (unsigned int i = 0; i < num_controls; i++) { + struct mixer_ctl *ctl = mixer_get_ctl(mixer, i); + const char *name = mixer_ctl_get_name(ctl); + if (!name) continue; + + int rank = get_dac_control_playback_priority(name); + if (rank < 0 || rank != best_rank) continue; + + enum mixer_ctl_type type = mixer_ctl_get_type(ctl); + unsigned int num_values = mixer_ctl_get_num_values(ctl); + int volume_control = strstr(name, "Volume") || strstr(name, "volume"); + int switch_control = strstr(name, "Switch") || strstr(name, "switch"); + + if (volume_control && type == MIXER_CTL_TYPE_INT) { + int min = mixer_ctl_get_range_min(ctl); + int max = mixer_ctl_get_range_max(ctl); + int volume = min + (val * (max - min)) / 100; + for (unsigned int j = 0; j < num_values; j++) + mixer_ctl_set_value(ctl, j, volume); + } + else if (switch_control && type == MIXER_CTL_TYPE_BOOL) { + // the lowest step could still be audible, mute for real at 0 + for (unsigned int j = 0; j < num_values; j++) + mixer_ctl_set_value(ctl, j, val > 0); + } + } mixer_close(mixer); } else { diff --git a/workspace/h700/libmsettings/msettings.h b/workspace/h700/libmsettings/msettings.h index e75bc6852..5f2900ff6 100644 --- a/workspace/h700/libmsettings/msettings.h +++ b/workspace/h700/libmsettings/msettings.h @@ -51,7 +51,7 @@ void SetJack(int value); // 0-1 #define AUDIO_SINK_DEFAULT 0 // use system default, usually speaker (or jack if plugged in) #define AUDIO_SINK_BLUETOOTH 1 // software control via bluealsa, not a separate card -#define AUDIO_SINK_USBDAC 2 // assumes being exposed as card 1 to alsa +#define AUDIO_SINK_USBDAC 2 // USB DAC card, monitored by audiomon int GetAudioSink(void); void SetAudioSink(int value); diff --git a/workspace/tg5040/libmsettings/msettings.c b/workspace/tg5040/libmsettings/msettings.c index 9b1c11edd..39d786189 100644 --- a/workspace/tg5040/libmsettings/msettings.c +++ b/workspace/tg5040/libmsettings/msettings.c @@ -1269,6 +1269,33 @@ static int get_a2dp_simple_control_name(char *buf, size_t buflen) { } static int get_usbc_card_num() { + // first choice - .asoundrc maintained by audiomon (card from ctl.!default section) + // second choice - .asoundrc maintained by audiomon (any card) + const char *userdata = getenv("USERDATA_PATH"); + if (userdata) { + char path[512]; + snprintf(path, sizeof(path), "%s/.asoundrc", userdata); + FILE *rc = fopen(path, "r"); + if (rc) { + char line[256]; + int in_ctl = 0, ctl_card = -1, any_card = -1, card_num; + while (fgets(line, sizeof(line), rc)) { + const char *p = line; + while (*p == ' ' || *p == '\t') p++; + if (strncmp(p, "ctl.", 4) == 0) in_ctl = 1; + else if (strncmp(p, "pcm.", 4) == 0) in_ctl = 0; + else if (strncmp(p, "card ", 5) == 0 && sscanf(p + 5, "%d", &card_num) == 1) { + if (in_ctl && ctl_card < 0) ctl_card = card_num; + if (any_card < 0) any_card = card_num; + } + } + fclose(rc); + if (ctl_card >= 0) return ctl_card; + if (any_card >= 0) return any_card; + } + } + + // fallback - first non-built-in card FILE *fp = popen("cat /proc/asound/cards", "r"); if (!fp) return -1; @@ -1306,6 +1333,38 @@ static int get_audiocodec_card_num() { return -1; } +// The rank of a mixer control of a USB audio device as its output volume, or -1 for a +// control that is not the output: capture, the microphone, and an input played into +// the output (sidetone, monitor, loopback, bypass, line in) +static int get_dac_control_playback_priority(const char* control_name) { + int capture_control = strstr(control_name, "Capture") + || strstr(control_name, "Mic"); + + int monitor_control = strstr(control_name, "Sidetone") + || strstr(control_name, "Monitor") + || strstr(control_name, "Loopback") + || strstr(control_name, "Line Playback") + || strstr(control_name, " In ") + || strstr(control_name, "Bypass"); + + if (capture_control || monitor_control) return -1; + + if (strstr(control_name, "Master")) return 3; + + if (strstr(control_name, "Headphone") + || strstr(control_name, "Headset") + || strstr(control_name, "Speaker") + || strstr(control_name, "Line Out") + || strstr(control_name, "Digital Out") + || strstr(control_name, "IEC958")) return 2; + + if (strstr(control_name, "PCM")) return 1; + + if (strstr(control_name, "Playback")) return 0; + + return -1; +} + void SetRawVolume(int val) { // in: 0-100 if (settings->mute && GetMutedVolume() != SETTINGS_DEFAULT_MUTE_NO_CHANGE) val = scaleVolume(GetMutedVolume()); @@ -1322,7 +1381,6 @@ void SetRawVolume(int val) { // in: 0-100 } } else if (GetAudioSink() == AUDIO_SINK_USBDAC) { - // USB DAC path: grab the first card that is not called "audiocodec" int card_num = get_usbc_card_num(); if(card_num < 0) { printf("Failed to find USB audio card\n"); fflush(stdout); @@ -1335,24 +1393,47 @@ void SetRawVolume(int val) { // in: 0-100 return; } - const unsigned int num_controls = mixer_get_num_ctls(mixer); - for (unsigned int i = 0; i < num_controls; i++) { - struct mixer_ctl *ctl = mixer_get_ctl(mixer, i); - const char *name = mixer_ctl_get_name(ctl); - if (!name) continue; - - if ((strstr(name, "PCM") || strstr(name, "Playback")) && (strstr(name, "Volume") || strstr(name, "volume"))) { - if (mixer_ctl_get_type(ctl) == MIXER_CTL_TYPE_INT) { - int min = mixer_ctl_get_range_min(ctl); - int max = mixer_ctl_get_range_max(ctl); - int volume = min + (val * (max - min)) / 100; - unsigned int num_values = mixer_ctl_get_num_values(ctl); - for (unsigned int i = 0; i < num_values; i++) - mixer_ctl_set_value(ctl, i, volume); - } - break; - } - } + const unsigned int num_controls = mixer_get_num_ctls(mixer); + + // find the best rank among the volume controls (-1 means not found) + int best_rank = -1; + for (unsigned int i = 0; i < num_controls; i++) { + struct mixer_ctl *ctl = mixer_get_ctl(mixer, i); + const char *name = mixer_ctl_get_name(ctl); + if (!name) continue; + if (!strstr(name, "Volume") && !strstr(name, "volume")) continue; + + int rank = get_dac_control_playback_priority(name); + if (rank > best_rank) best_rank = rank; + } + + // drive each volume and switch of that rank; the other controls keep their level + for (unsigned int i = 0; i < num_controls; i++) { + struct mixer_ctl *ctl = mixer_get_ctl(mixer, i); + const char *name = mixer_ctl_get_name(ctl); + if (!name) continue; + + int rank = get_dac_control_playback_priority(name); + if (rank < 0 || rank != best_rank) continue; + + enum mixer_ctl_type type = mixer_ctl_get_type(ctl); + unsigned int num_values = mixer_ctl_get_num_values(ctl); + int volume_control = strstr(name, "Volume") || strstr(name, "volume"); + int switch_control = strstr(name, "Switch") || strstr(name, "switch"); + + if (volume_control && type == MIXER_CTL_TYPE_INT) { + int min = mixer_ctl_get_range_min(ctl); + int max = mixer_ctl_get_range_max(ctl); + int volume = min + (val * (max - min)) / 100; + for (unsigned int j = 0; j < num_values; j++) + mixer_ctl_set_value(ctl, j, volume); + } + else if (switch_control && type == MIXER_CTL_TYPE_BOOL) { + // the lowest step could still be audible, mute for real at 0 + for (unsigned int j = 0; j < num_values; j++) + mixer_ctl_set_value(ctl, j, val > 0); + } + } mixer_close(mixer); } else { diff --git a/workspace/tg5040/libmsettings/msettings.h b/workspace/tg5040/libmsettings/msettings.h index db46458bf..47ae76ce4 100644 --- a/workspace/tg5040/libmsettings/msettings.h +++ b/workspace/tg5040/libmsettings/msettings.h @@ -51,7 +51,7 @@ void SetJack(int value); // 0-1 #define AUDIO_SINK_DEFAULT 0 // use system default, usually speaker (or jack if plugged in) #define AUDIO_SINK_BLUETOOTH 1 // software control via bluealsa, not a separate card -#define AUDIO_SINK_USBDAC 2 // assumes being exposed as card 1 to alsa +#define AUDIO_SINK_USBDAC 2 // USB DAC card, monitored by audiomon int GetAudioSink(void); void SetAudioSink(int value); diff --git a/workspace/tg5050/libmsettings/msettings.c b/workspace/tg5050/libmsettings/msettings.c index a8ddea691..12a343b12 100644 --- a/workspace/tg5050/libmsettings/msettings.c +++ b/workspace/tg5050/libmsettings/msettings.c @@ -925,6 +925,33 @@ static int get_a2dp_simple_control_name(char *buf, size_t buflen) { } static int get_usbc_card_num() { + // first choice - .asoundrc maintained by audiomon (card from ctl.!default section) + // second choice - .asoundrc maintained by audiomon (any card) + const char *userdata = getenv("USERDATA_PATH"); + if (userdata) { + char path[512]; + snprintf(path, sizeof(path), "%s/.asoundrc", userdata); + FILE *rc = fopen(path, "r"); + if (rc) { + char line[256]; + int in_ctl = 0, ctl_card = -1, any_card = -1, card_num; + while (fgets(line, sizeof(line), rc)) { + const char *p = line; + while (*p == ' ' || *p == '\t') p++; + if (strncmp(p, "ctl.", 4) == 0) in_ctl = 1; + else if (strncmp(p, "pcm.", 4) == 0) in_ctl = 0; + else if (strncmp(p, "card ", 5) == 0 && sscanf(p + 5, "%d", &card_num) == 1) { + if (in_ctl && ctl_card < 0) ctl_card = card_num; + if (any_card < 0) any_card = card_num; + } + } + fclose(rc); + if (ctl_card >= 0) return ctl_card; + if (any_card >= 0) return any_card; + } + } + + // fallback - first non-built-in card FILE *fp = popen("cat /proc/asound/cards", "r"); if (!fp) return -1; @@ -962,6 +989,38 @@ static int get_audiocodec_card_num() { return -1; } +// The rank of a mixer control of a USB audio device as its output volume, or -1 for a +// control that is not the output: capture, the microphone, and an input played into +// the output (sidetone, monitor, loopback, bypass, line in) +static int get_dac_control_playback_priority(const char* control_name) { + int capture_control = strstr(control_name, "Capture") + || strstr(control_name, "Mic"); + + int monitor_control = strstr(control_name, "Sidetone") + || strstr(control_name, "Monitor") + || strstr(control_name, "Loopback") + || strstr(control_name, "Line Playback") + || strstr(control_name, " In ") + || strstr(control_name, "Bypass"); + + if (capture_control || monitor_control) return -1; + + if (strstr(control_name, "Master")) return 3; + + if (strstr(control_name, "Headphone") + || strstr(control_name, "Headset") + || strstr(control_name, "Speaker") + || strstr(control_name, "Line Out") + || strstr(control_name, "Digital Out") + || strstr(control_name, "IEC958")) return 2; + + if (strstr(control_name, "PCM")) return 1; + + if (strstr(control_name, "Playback")) return 0; + + return -1; +} + void SetRawVolume(int val) { // in: 0-100 if (settings->mute && GetMutedVolume() != SETTINGS_DEFAULT_MUTE_NO_CHANGE) val = scaleVolume(GetMutedVolume()); @@ -978,7 +1037,6 @@ void SetRawVolume(int val) { // in: 0-100 } } else if (GetAudioSink() == AUDIO_SINK_USBDAC) { - // USB DAC path: grab the first card that is not called "audiocodec" int card_num = get_usbc_card_num(); if(card_num < 0) { printf("Failed to find USB audio card\n"); fflush(stdout); @@ -991,24 +1049,47 @@ void SetRawVolume(int val) { // in: 0-100 return; } - const unsigned int num_controls = mixer_get_num_ctls(mixer); - for (unsigned int i = 0; i < num_controls; i++) { - struct mixer_ctl *ctl = mixer_get_ctl(mixer, i); - const char *name = mixer_ctl_get_name(ctl); - if (!name) continue; - - if ((strstr(name, "PCM") || strstr(name, "Playback")) && (strstr(name, "Volume") || strstr(name, "volume"))) { - if (mixer_ctl_get_type(ctl) == MIXER_CTL_TYPE_INT) { - int min = mixer_ctl_get_range_min(ctl); - int max = mixer_ctl_get_range_max(ctl); - int volume = min + (val * (max - min)) / 100; - unsigned int num_values = mixer_ctl_get_num_values(ctl); - for (unsigned int i = 0; i < num_values; i++) - mixer_ctl_set_value(ctl, i, volume); - } - break; - } - } + const unsigned int num_controls = mixer_get_num_ctls(mixer); + + // find the best rank among the volume controls (-1 means not found) + int best_rank = -1; + for (unsigned int i = 0; i < num_controls; i++) { + struct mixer_ctl *ctl = mixer_get_ctl(mixer, i); + const char *name = mixer_ctl_get_name(ctl); + if (!name) continue; + if (!strstr(name, "Volume") && !strstr(name, "volume")) continue; + + int rank = get_dac_control_playback_priority(name); + if (rank > best_rank) best_rank = rank; + } + + // drive each volume and switch of that rank; the other controls keep their level + for (unsigned int i = 0; i < num_controls; i++) { + struct mixer_ctl *ctl = mixer_get_ctl(mixer, i); + const char *name = mixer_ctl_get_name(ctl); + if (!name) continue; + + int rank = get_dac_control_playback_priority(name); + if (rank < 0 || rank != best_rank) continue; + + enum mixer_ctl_type type = mixer_ctl_get_type(ctl); + unsigned int num_values = mixer_ctl_get_num_values(ctl); + int volume_control = strstr(name, "Volume") || strstr(name, "volume"); + int switch_control = strstr(name, "Switch") || strstr(name, "switch"); + + if (volume_control && type == MIXER_CTL_TYPE_INT) { + int min = mixer_ctl_get_range_min(ctl); + int max = mixer_ctl_get_range_max(ctl); + int volume = min + (val * (max - min)) / 100; + for (unsigned int j = 0; j < num_values; j++) + mixer_ctl_set_value(ctl, j, volume); + } + else if (switch_control && type == MIXER_CTL_TYPE_BOOL) { + // the lowest step could still be audible, mute for real at 0 + for (unsigned int j = 0; j < num_values; j++) + mixer_ctl_set_value(ctl, j, val > 0); + } + } mixer_close(mixer); } else { diff --git a/workspace/tg5050/libmsettings/msettings.h b/workspace/tg5050/libmsettings/msettings.h index ae756b649..f16a85b2e 100644 --- a/workspace/tg5050/libmsettings/msettings.h +++ b/workspace/tg5050/libmsettings/msettings.h @@ -54,7 +54,7 @@ void SetJack(int value); // 0-1 #define AUDIO_SINK_DEFAULT 0 // use system default, usually speaker (or jack if plugged in) #define AUDIO_SINK_BLUETOOTH 1 // software control via bluealsa, not a separate card -#define AUDIO_SINK_USBDAC 2 // assumes being exposed as card 1 to alsa +#define AUDIO_SINK_USBDAC 2 // USB DAC card, monitored by audiomon int GetAudioSink(void); void SetAudioSink(int value);