From 7997e76b0269b2c335bda37f5a1f4604471565a5 Mon Sep 17 00:00:00 2001 From: "Geoffrey D. Bennett" Date: Tue, 12 Mar 2024 00:43:52 +1030 Subject: [PATCH] Add display of sample rate --- src/alsa-scarlett-gui.css | 25 ++++++++ src/iface-mixer.c | 50 ++++++++++++---- src/widget-sample-rate.c | 123 ++++++++++++++++++++++++++++++++++++++ src/widget-sample-rate.h | 12 ++++ 4 files changed, 199 insertions(+), 11 deletions(-) create mode 100644 src/widget-sample-rate.c create mode 100644 src/widget-sample-rate.h diff --git a/src/alsa-scarlett-gui.css b/src/alsa-scarlett-gui.css index c93f889..e2cf599 100644 --- a/src/alsa-scarlett-gui.css +++ b/src/alsa-scarlett-gui.css @@ -228,6 +228,31 @@ button.toggle { text-shadow: 0 0 5px #00c000, 0 0 15px #00c000; } +/* Sample Rates */ +.window-frame button.sample-rate.sample-rate-44100 { + text-shadow: 0 0 5px #00c000, 0 0 15px #00c000; +} + +.window-frame button.sample-rate.sample-rate-48000 { + text-shadow: 0 0 5px #00c000, 0 0 15px #00c000; +} + +.window-frame button.sample-rate.sample-rate-88200 { + text-shadow: 0 0 5px #ff8000, 0 0 15px #ff8000; +} + +.window-frame button.sample-rate.sample-rate-96000 { + text-shadow: 0 0 5px #ff8000, 0 0 15px #ff8000; +} + +.window-frame button.sample-rate.sample-rate-176400 { + text-shadow: 0 0 5px #ff0000, 0 0 15px #c00000; +} + +.window-frame button.sample-rate.sample-rate-192000 { + text-shadow: 0 0 5px #ff0000, 0 0 15px #c00000; +} + /* Button controls where checked is dimmer */ /* Mute button */ diff --git a/src/iface-mixer.c b/src/iface-mixer.c index 7e16fc9..1178660 100644 --- a/src/iface-mixer.c +++ b/src/iface-mixer.c @@ -11,6 +11,7 @@ #include "widget-gain.h" #include "widget-input-select.h" #include "widget-label.h" +#include "widget-sample-rate.h" #include "window-helper.h" #include "window-levels.h" #include "window-mixer.h" @@ -114,6 +115,30 @@ static void add_power_status_control( gtk_box_append(GTK_BOX(b), w); } +static void add_sample_rate_control( + struct alsa_card *card, + GtkWidget *global_controls +) { + GtkWidget *b = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5); + gtk_widget_set_tooltip_text( + b, + "The Sample Rate cannot be changed here because it is set by the " + "application which is using the interface, usually a sound " + "server like PulseAudio, JACK, or PipeWire. If this shows N/A, " + "no application is currently using the interface.\n\n" + "Note that not all features are available on all interfaces at " + "sample rates above 48kHz. Please refer to the user guide for " + "your interface for more information." + ); + gtk_box_append(GTK_BOX(global_controls), b); + + GtkWidget *l = gtk_label_new("Sample Rate"); + gtk_box_append(GTK_BOX(b), l); + GtkWidget *w = make_sample_rate_widget(card); + gtk_widget_add_css_class(w, "sample-rate"); + gtk_box_append(GTK_BOX(b), w); +} + static void add_speaker_switching_controls( struct alsa_card *card, GtkWidget *global_controls @@ -690,21 +715,24 @@ static void create_global_controls( ? GTK_ORIENTATION_HORIZONTAL : GTK_ORIENTATION_VERTICAL; GtkWidget *global_controls = create_global_box(top, x, orient); - GtkWidget *left = global_controls; - GtkWidget *right = global_controls; + GtkWidget *column[3]; + + for (int i = 0; i < 3; i++) + column[i] = global_controls; if (card->has_speaker_switching) { - left = gtk_box_new(GTK_ORIENTATION_VERTICAL, 15); - right = gtk_box_new(GTK_ORIENTATION_VERTICAL, 15); - gtk_box_append(GTK_BOX(global_controls), left); - gtk_box_append(GTK_BOX(global_controls), right); + for (int i = 0; i < 3; i++) { + column[i] = gtk_box_new(GTK_ORIENTATION_VERTICAL, 15); + gtk_box_append(GTK_BOX(global_controls), column[i]); + } } - add_clock_source_control(card, left); - add_sync_status_control(card, right); - add_power_status_control(card, right); - add_speaker_switching_controls(card, left); - add_talkback_controls(card, right); + add_clock_source_control(card, column[0]); + add_sync_status_control(card, column[1]); + add_power_status_control(card, column[1]); + add_sample_rate_control(card, column[2]); + add_speaker_switching_controls(card, column[0]); + add_talkback_controls(card, column[1]); } static GtkWidget *create_main_window_controls(struct alsa_card *card) { diff --git a/src/widget-sample-rate.c b/src/widget-sample-rate.c new file mode 100644 index 0000000..61c3fe8 --- /dev/null +++ b/src/widget-sample-rate.c @@ -0,0 +1,123 @@ +// SPDX-FileCopyrightText: 2024 Geoffrey D. Bennett +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "gtkhelper.h" +#include "widget-boolean.h" + +struct sample_rate { + struct alsa_card *card; + GtkWidget *button; + guint source; + char *path; + int sample_rate; +}; + +static void button_set_text(GtkWidget *button, int value) { + gtk_widget_remove_css_classes_by_prefix(button, "sample-rate-"); + + if (!value) { + gtk_button_set_label(GTK_BUTTON(button), "N/A"); + return; + } + + char *text; + if (value % 1000 == 0) + text = g_strdup_printf("%dkHz", value / 1000); + else + text = g_strdup_printf("%.1fkHz", value / 1000.0); + gtk_button_set_label(GTK_BUTTON(button), text); + g_free(text); + + char *css_class = g_strdup_printf( + "sample-rate-%d", value + ); + gtk_widget_add_css_class(button, css_class); + g_free(css_class); +} + +// Read the sample rate from /proc/asound/cardN/stream0 +// and return it as an integer +// +// Looking for a line containing: +// Momentary freq = 48000 Hz (0x6.0000) +static int get_sample_rate(struct sample_rate *data) { + if (!data->path) + return 0; + + FILE *file = fopen(data->path, "r"); + if (!file) { + perror("fopen"); + return 0; + } + + char *line = NULL; + size_t len = 0; + ssize_t read; + + int sample_rate = 0; + + while ((read = getline(&line, &len, file)) != -1) { + if (strstr(line, "Momentary freq = ")) { + char *start = strstr(line, "Momentary freq = ") + 17; + char *end = strstr(start, " Hz"); + + if (!start || !end) + continue; + + *end = '\0'; + sample_rate = atoi(start); + + break; + } + } + + free(line); + fclose(file); + + return sample_rate; +} + +static gboolean update_sample_rate(struct sample_rate *data) { + int sample_rate = get_sample_rate(data); + + if (sample_rate != data->sample_rate) { + data->sample_rate = sample_rate; + button_set_text(data->button, sample_rate); + } + + return G_SOURCE_CONTINUE; +} + +static void on_destroy(struct sample_rate *data, GObject *widget) { + if (data->source) + g_source_remove(data->source); + g_free(data->path); + g_free(data); +} + +GtkWidget *make_sample_rate_widget( + struct alsa_card *card +) { + struct sample_rate *data = g_malloc0(sizeof(struct sample_rate)); + data->card = card; + data->button = gtk_toggle_button_new(); + data->sample_rate = -1; + + gtk_widget_add_css_class(data->button, "fixed"); + gtk_widget_add_css_class(data->button, "sample-rate"); + + // can only update if it's a real card + if (card->num != SIMULATED_CARD_NUM) { + data->path = g_strdup_printf("/proc/asound/card%d/stream0", card->num); + data->source = + g_timeout_add_seconds(1, (GSourceFunc)update_sample_rate, data); + } + + // initial update (will show "N/A" for simulated card) + update_sample_rate(data); + + // cleanup when the button is destroyed + g_object_weak_ref(G_OBJECT(data->button), (GWeakNotify)on_destroy, data); + + return data->button; +} diff --git a/src/widget-sample-rate.h b/src/widget-sample-rate.h new file mode 100644 index 0000000..cf643c5 --- /dev/null +++ b/src/widget-sample-rate.h @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: 2024 Geoffrey D. Bennett +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +#include "alsa.h" + +GtkWidget *make_sample_rate_widget( + struct alsa_card *alsa_card +);