This repository has been archived on 2025-09-15. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
alsa-scarlett-gui/src/widget-gain.c
2025-02-21 04:08:35 +10:30

256 lines
6.7 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// SPDX-FileCopyrightText: 2022-2025 Geoffrey D. Bennett <g@b4.vu>
// SPDX-License-Identifier: GPL-3.0-or-later
#include "gtkdial.h"
#include "stringhelper.h"
#include "widget-gain.h"
#include "db.h"
struct gain {
struct alsa_elem *elem;
struct alsa_elem *direct_monitor_elem;
struct alsa_elem *monitor_mix_elem[2];
GtkWidget *vbox;
GtkWidget *dial;
GtkWidget *label;
int zero_is_off;
float scale;
};
static void gain_changed(GtkWidget *widget, struct gain *data) {
int value = gtk_dial_get_value(GTK_DIAL(data->dial));
alsa_set_elem_value(data->elem, value);
// check if there is a corresponding Direct Monitor Mix control to
// update as well
// Direct Monitor control?
if (!data->direct_monitor_elem)
return;
// Direct Monitor enabled?
int direct_monitor = alsa_get_elem_value(data->direct_monitor_elem);
if (!direct_monitor)
return;
// Get the corresponding Mix control
struct alsa_elem *monitor_mix = data->monitor_mix_elem[direct_monitor - 1];
if (!monitor_mix)
return;
// Update it
alsa_set_elem_value(monitor_mix, value);
}
static void gain_updated(
struct alsa_elem *elem,
void *private
) {
struct gain *data = private;
int is_writable = alsa_get_elem_writable(elem);
gtk_widget_set_sensitive(data->dial, is_writable);
int alsa_value = alsa_get_elem_value(elem);
gtk_dial_set_value(GTK_DIAL(data->dial), alsa_value);
char s[20];
char *p = s;
float value;
int min_db = round(elem->min_cdB / 100.0);
int max_db = round(elem->max_cdB / 100.0);
if (elem->dB_type == SND_CTL_TLVT_DB_LINEAR) {
value = linear_value_to_db(
alsa_value,
elem->min_val,
elem->max_val,
min_db,
max_db
);
} else {
value = ((float)(alsa_value - elem->min_val)) * data->scale + (elem->min_cdB / 100.0);
if (value > max_db)
value = max_db;
else if (value < min_db)
value = min_db;
}
if (data->zero_is_off && value == min_db) {
p += sprintf(p, "−∞");
} else {
if (data->scale <= 0.5)
value = round(value * 10) / 10;
if (value < 0)
p += sprintf(p, "");
else if (value > 0)
p += sprintf(p, "+");
if (data->scale <= 0.5)
p += snprintf(p, 10, "%.1f", fabs(value));
else
p += snprintf(p, 10, "%.0f", fabs(value));
}
if (data->scale > 0.5)
p += sprintf(p, "dB");
gtk_label_set_text(GTK_LABEL(data->label), s);
}
// 4th Gen Solo and 2i2 have Mix & Direct Monitor controls which
// interact. If direct monitor is enabled and the Mix A/B controls are
// changed, then the Monitor Mix Playback Volume controls are changed
// too so that the mix settings are restored when direct monitor is
// later enabled again.
static void find_direct_monitor_controls(struct gain *data) {
struct alsa_elem *elem = data->elem;
GArray *elems = elem->card->elems;
// Card has no direct monitor control?
struct alsa_elem *direct_monitor_elem = get_elem_by_prefix(
elems,
"Direct Monitor Playback"
);
if (!direct_monitor_elem)
return;
// Card has no mixer?
if (strncmp(elem->name, "Mix ", 4) != 0 ||
!strstr(elem->name, "Playback Volume"))
return;
char mix_letter = elem->name[4];
int input_num = get_num_from_string(elem->name);
// Find the Monitor Mix control for the 4th Gen Solo
if (strstr(direct_monitor_elem->name, "Switch")) {
char s[80];
sprintf(
s,
"Monitor Mix %c Input %02d Playback Volume",
mix_letter, input_num
);
struct alsa_elem *monitor_mix_elem = get_elem_by_name(elems, s);
if (!monitor_mix_elem)
return;
data->direct_monitor_elem = direct_monitor_elem;
data->monitor_mix_elem[0] = monitor_mix_elem;
// Find the Monitor Mix controls for the 4th Gen 2i2
} else if (strstr(direct_monitor_elem->name, "Enum")) {
for (int i = 0; i <= 1; i++) {
char s[80];
sprintf(
s,
"Monitor %d Mix %c Input %02d Playback Volume",
i + 1, mix_letter, input_num
);
struct alsa_elem *monitor_mix_elem = get_elem_by_name(elems, s);
if (!monitor_mix_elem)
return;
data->direct_monitor_elem = direct_monitor_elem;
data->monitor_mix_elem[i] = monitor_mix_elem;
}
} else {
fprintf(stderr, "Couldn't find direct monitor mix control\n");
}
}
//GList *make_gain_alsa_elem(struct alsa_elem *elem) {
GtkWidget *make_gain_alsa_elem(
struct alsa_elem *elem,
int zero_is_off,
int widget_taper,
int can_control
) {
struct gain *data = calloc(1, sizeof(struct gain));
data->elem = elem;
data->vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
gtk_widget_set_hexpand(data->vbox, TRUE);
gtk_widget_set_valign(data->vbox, GTK_ALIGN_START);
gtk_widget_set_vexpand(data->vbox, TRUE);
gboolean is_linear = elem->dB_type == SND_CTL_TLVT_DB_LINEAR;
double step;
if (is_linear) {
data->scale = 0.5;
step = 0.5;
} else {
data->scale = (float)(elem->max_cdB - elem->min_cdB) / 100.0 /
(elem->max_val - elem->min_val);
step = 1;
}
data->dial = gtk_dial_new_with_range(
elem->min_val,
elem->max_val,
step,
3 / data->scale
);
// calculate 0dB value
int zero_db_value;
if (is_linear) {
zero_db_value = cdb_to_linear_value(
0,
elem->min_val,
elem->max_val,
elem->min_cdB,
elem->max_cdB
);
} else {
zero_db_value =
(int)((0 - elem->min_cdB) / 100.0 / data->scale + elem->min_val);
}
gtk_dial_set_zero_db(GTK_DIAL(data->dial), zero_db_value);
gtk_dial_set_is_linear(GTK_DIAL(data->dial), is_linear);
// convert from widget_taper to gtk_dial_taper
int gtk_dial_taper;
if (widget_taper == WIDGET_GAIN_TAPER_LINEAR)
gtk_dial_taper = GTK_DIAL_TAPER_LINEAR;
else if (widget_taper == WIDGET_GAIN_TAPER_LOG)
gtk_dial_taper = GTK_DIAL_TAPER_LOG;
else
gtk_dial_taper = GTK_DIAL_TAPER_LINEAR;
gtk_dial_set_taper(GTK_DIAL(data->dial), gtk_dial_taper);
if (widget_taper == WIDGET_GAIN_TAPER_GEN4_VOLUME)
gtk_dial_set_taper_linear_breakpoints(
GTK_DIAL(data->dial),
(const double[]){ 0.488, 0.76 },
(const double[]){ 0.07, 0.4 },
2
);
gtk_dial_set_can_control(GTK_DIAL(data->dial), can_control);
data->label = gtk_label_new(NULL);
gtk_widget_add_css_class(data->label, "gain");
gtk_widget_set_vexpand(data->dial, TRUE);
data->zero_is_off = zero_is_off;
find_direct_monitor_controls(data);
g_signal_connect(
data->dial, "value-changed", G_CALLBACK(gain_changed), data
);
alsa_elem_add_callback(elem, gain_updated, data);
gain_updated(elem, data);
gtk_box_append(GTK_BOX(data->vbox), data->dial);
gtk_box_append(GTK_BOX(data->vbox), data->label);
return data->vbox;
}