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/iface-mixer.c
Geoffrey D. Bennett 7c9ec68d7c Fix input counting for Clarett+ series
The Clarett+ series don't have a pad function, so can't rely on that
for creating input controls. Look for "Line ... Capture Switch" rather
than "Line ... Pad Capture Switch".
2023-09-20 03:36:53 +09:30

471 lines
14 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 Geoffrey D. Bennett <g@b4.vu>
// SPDX-License-Identifier: GPL-3.0-or-later
#include "gtkhelper.h"
#include "iface-mixer.h"
#include "stringhelper.h"
#include "tooltips.h"
#include "widget-boolean.h"
#include "widget-combo.h"
#include "widget-dual.h"
#include "widget-volume.h"
#include "window-helper.h"
#include "window-levels.h"
#include "window-mixer.h"
#include "window-routing.h"
#include "window-startup.h"
static void add_clock_source_control(
struct alsa_card *card,
GtkWidget *global_controls
) {
GArray *elems = card->elems;
struct alsa_elem *clock_source = get_elem_by_prefix(elems, "Clock Source");
if (!clock_source)
return;
GtkWidget *b = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5);
gtk_widget_set_tooltip_text(
b,
"Clock Source selects where the interface receives its digital "
"clock from. If you arent using S/PDIF or ADAT inputs, set this "
"to Internal."
);
gtk_box_append(GTK_BOX(global_controls), b);
GtkWidget *l = gtk_label_new("Clock Source");
GtkWidget *w = make_combo_box_alsa_elem(clock_source);
gtk_box_append(GTK_BOX(b), l);
gtk_box_append(GTK_BOX(b), w);
}
static void add_sync_status_control(
struct alsa_card *card,
GtkWidget *global_controls
) {
GArray *elems = card->elems;
struct alsa_elem *sync_status = get_elem_by_name(elems, "Sync Status");
if (!sync_status)
return;
GtkWidget *b = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5);
if (get_elem_by_prefix(elems, "Clock Source")) {
gtk_widget_set_tooltip_text(
b,
"Sync Status indicates if the interface is locked to a valid "
"digital clock. If you arent using S/PDIF or ADAT inputs and "
"the Sync Status is Unlocked, change the Clock Source to "
"Internal."
);
} else {
gtk_widget_set_tooltip_text(
b,
"Sync Status indicates if the interface is locked to a valid "
"digital clock. Since the Clock Source is fixed to internal on "
"this interface, this should stay locked."
);
}
gtk_box_append(GTK_BOX(global_controls), b);
GtkWidget *l = gtk_label_new("Sync Status");
gtk_box_append(GTK_BOX(b), l);
GtkWidget *w = make_boolean_alsa_elem(sync_status, "Unlocked", "Locked");
gtk_box_append(GTK_BOX(b), w);
}
static void add_speaker_switching_controls(
struct alsa_card *card,
GtkWidget *global_controls
) {
GArray *elems = card->elems;
struct alsa_elem *speaker_switching = get_elem_by_name(
elems, "Speaker Switching Playback Enum"
);
if (!speaker_switching)
return;
make_dual_boolean_alsa_elems(speaker_switching, "Off", "On", "Main", "Alt");
GtkWidget *b = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5);
gtk_widget_set_tooltip_text(
b,
"Speaker Switching lets you swap between two pairs of "
"monitoring speakers very easily."
);
GtkWidget *l = gtk_label_new("Speaker Switching");
gtk_box_append(GTK_BOX(global_controls), b);
gtk_box_append(GTK_BOX(b), l);
gtk_box_append(GTK_BOX(b), speaker_switching->widget);
gtk_box_append(GTK_BOX(b), speaker_switching->widget2);
}
static void add_talkback_controls(
struct alsa_card *card,
GtkWidget *global_controls
) {
GArray *elems = card->elems;
struct alsa_elem *talkback = get_elem_by_name(
elems, "Talkback Playback Enum"
);
if (!talkback)
return;
make_dual_boolean_alsa_elems(talkback, "Disabled", "Enabled", "Off", "On");
GtkWidget *b = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5);
gtk_widget_set_tooltip_text(
b,
"Talkback lets you add another channel (usually the talkback "
"mic) to a mix with a button push, usually to talk to "
"musicians, and without using an additional mic channel."
);
GtkWidget *l = gtk_label_new("Talkback");
gtk_box_append(GTK_BOX(global_controls), b);
gtk_box_append(GTK_BOX(b), l);
gtk_box_append(GTK_BOX(b), talkback->widget);
gtk_box_append(GTK_BOX(b), talkback->widget2);
}
static GtkWidget *create_global_box(GtkWidget *grid, int *x, int orient) {
GtkWidget *label = gtk_label_new("Global");
GtkWidget *sep = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL);
GtkWidget *controls = gtk_box_new(orient, 15);
gtk_widget_set_margin(controls, 10);
gtk_grid_attach(GTK_GRID(grid), label, *x, 0, 1, 1);
gtk_grid_attach(GTK_GRID(grid), sep, *x, 1, 1, 1);
gtk_grid_attach(GTK_GRID(grid), controls, *x, 2, 1, 1);
(*x)++;
return controls;
}
static void create_input_controls(
struct alsa_card *card,
GtkWidget *top,
int *x
) {
GArray *elems = card->elems;
// find how many inputs have switches
int input_count = get_max_elem_by_name(elems, "Line", "Capture Switch");
// Only the 18i20 Gen 2 has no input controls
if (!input_count)
return;
GtkWidget *sep = gtk_separator_new(GTK_ORIENTATION_VERTICAL);
gtk_widget_set_halign(sep, GTK_ALIGN_CENTER);
gtk_grid_attach(GTK_GRID(top), sep, (*x)++, 0, 1, 3);
GtkWidget *label_ic = gtk_label_new("Analogue Inputs");
gtk_grid_attach(GTK_GRID(top), label_ic, *x, 0, 1, 1);
GtkWidget *horiz_input_sep = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL);
gtk_grid_attach(GTK_GRID(top), horiz_input_sep, *x, 1, 1, 1);
GtkWidget *input_grid = gtk_grid_new();
gtk_grid_set_spacing(GTK_GRID(input_grid), 10);
gtk_grid_attach(GTK_GRID(top), input_grid, *x, 2, 1, 1);
for (int i = 1; i <= input_count; i++) {
char s[20];
snprintf(s, 20, "%d", i);
GtkWidget *label = gtk_label_new(s);
gtk_grid_attach(GTK_GRID(input_grid), label, i, 0, 1, 1);
}
GtkWidget *level_label = NULL;
GtkWidget *air_label = NULL;
GtkWidget *pad_label = NULL;
for (int i = 0; i < elems->len; i++) {
struct alsa_elem *elem = &g_array_index(elems, struct alsa_elem, i);
GtkWidget *w;
// if no card entry, it's an empty slot
if (!elem->card)
continue;
int line_num = get_num_from_string(elem->name);
// input controls
if (strstr(elem->name, "Level Capture Enum")) {
if (!level_label) {
level_label = gtk_label_new("Level");
gtk_grid_attach(GTK_GRID(input_grid), level_label, 0, 1, 1, 1);
}
w = make_boolean_alsa_elem(elem, "Line", "Inst");
gtk_widget_set_tooltip_text(w, level_descr);
gtk_grid_attach(GTK_GRID(input_grid), w, line_num, 1, 1, 1);
} else if (strstr(elem->name, "Air Capture Switch")) {
if (!air_label) {
air_label = gtk_label_new("Air");
gtk_grid_attach(GTK_GRID(input_grid), air_label, 0, 2, 1, 1);
}
w = make_boolean_alsa_elem(elem, "Off", "On");
gtk_widget_set_tooltip_text(w, air_descr);
gtk_grid_attach(GTK_GRID(input_grid), w, line_num, 2, 1, 1);
} else if (strstr(elem->name, "Pad Capture Switch")) {
if (!pad_label) {
pad_label = gtk_label_new("Pad");
gtk_grid_attach(GTK_GRID(input_grid), pad_label, 0, 3, 1, 1);
}
w = make_boolean_alsa_elem(elem, "Off", "On");
gtk_widget_set_tooltip_text(
w,
"Enabling Pad engages an attenuator in the channel, giving "
"you more headroom for very hot signals."
);
gtk_grid_attach(GTK_GRID(input_grid), w, line_num, 3, 1, 1);
} else if (strstr(elem->name, "Phantom Power Capture Switch")) {
int from, to;
get_two_num_from_string(elem->name, &from, &to);
w = make_boolean_alsa_elem(elem, "48V Off", "48V On");
gtk_widget_set_tooltip_text(w, phantom_descr);
gtk_grid_attach(GTK_GRID(input_grid), w, from, 4, to - from + 1, 1);
}
}
(*x)++;
}
static void create_output_controls(
struct alsa_card *card,
GtkWidget *top,
int *x,
int y,
int x_span
) {
GArray *elems = card->elems;
if (*x) {
GtkWidget *sep = gtk_separator_new(GTK_ORIENTATION_VERTICAL);
gtk_grid_attach(GTK_GRID(top), sep, (*x)++, y, x_span, 3);
}
GtkWidget *label_oc = gtk_label_new("Analogue Outputs");
gtk_grid_attach(GTK_GRID(top), label_oc, *x, y, x_span, 1);
GtkWidget *horiz_output_sep = gtk_separator_new(GTK_ORIENTATION_VERTICAL);
gtk_grid_attach(GTK_GRID(top), horiz_output_sep, *x, y + 1, x_span, 1);
GtkWidget *output_grid = gtk_grid_new();
gtk_grid_set_spacing(GTK_GRID(output_grid), 10);
gtk_grid_attach(GTK_GRID(top), output_grid, *x, y + 2, x_span, 1);
gtk_widget_set_hexpand(output_grid, TRUE);
int output_count = get_max_elem_by_name(elems, "Line", "Playback Volume");
int has_hw_vol = !!get_elem_by_name(elems, "Master HW Playback Volume");
int line_1_col = has_hw_vol;
for (int i = 0; i < output_count; i++) {
char s[20];
snprintf(s, 20, "%d", i + 1);
GtkWidget *label = gtk_label_new(s);
gtk_grid_attach(GTK_GRID(output_grid), label, i + line_1_col, 0, 1, 1);
}
for (int i = 0; i < elems->len; i++) {
struct alsa_elem *elem = &g_array_index(elems, struct alsa_elem, i);
GtkWidget *w;
// if no card entry, it's an empty slot
if (!elem->card)
continue;
int line_num = get_num_from_string(elem->name);
// output controls
if (strncmp(elem->name, "Line", 4) == 0) {
if (strstr(elem->name, "Playback Volume")) {
w = make_volume_alsa_elem(elem);
gtk_grid_attach(
GTK_GRID(output_grid), w, line_num - 1 + line_1_col, 1, 1, 1
);
} else if (strstr(elem->name, "Mute Playback Switch")) {
w = make_boolean_alsa_elem(
elem, "*audio-volume-high", "*audio-volume-muted"
);
if (has_hw_vol) {
gtk_widget_set_tooltip_text(
w,
"Mute (only available when under software control)"
);
} else {
gtk_widget_set_tooltip_text(w, "Mute");
}
gtk_grid_attach(
GTK_GRID(output_grid), w, line_num - 1 + line_1_col, 2, 1, 1
);
} else if (strstr(elem->name, "Volume Control Playback Enum")) {
w = make_boolean_alsa_elem(elem, "SW", "HW");
gtk_widget_set_tooltip_text(
w,
"Set software-controlled (SW) or hardware-controlled (HW) "
"volume for this analogue output."
);
gtk_grid_attach(
GTK_GRID(output_grid), w, line_num - 1 + line_1_col, 3, 1, 1
);
}
// master output controls
} else if (strcmp(elem->name, "Master HW Playback Volume") == 0) {
GtkWidget *l = gtk_label_new("HW");
gtk_widget_set_tooltip_text(
l,
"This control shows the setting of the physical (hardware) "
"volume knob, which controls the volume of the analogue "
"outputs which have been set to “HW”."
);
gtk_grid_attach(GTK_GRID(output_grid), l, 0, 0, 1, 1);
w = make_volume_alsa_elem(elem);
gtk_grid_attach(GTK_GRID(output_grid), w, 0, 1, 1, 1);
} else if (strcmp(elem->name, "Mute Playback Switch") == 0) {
w = make_boolean_alsa_elem(
elem, "*audio-volume-high", "*audio-volume-muted"
);
gtk_widget_set_tooltip_text(w, "Mute HW controlled outputs");
gtk_grid_attach(GTK_GRID(output_grid), elem->widget, 0, 2, 1, 1);
} else if (strcmp(elem->name, "Dim Playback Switch") == 0) {
w = make_boolean_alsa_elem(
elem, "*audio-volume-medium", "*audio-volume-low"
);
gtk_widget_set_tooltip_text(
w, "Dim (lower volume) of HW controlled outputs"
);
gtk_grid_attach(GTK_GRID(output_grid), w, 0, 3, 1, 1);
}
}
(*x)++;
}
static void create_global_controls(
struct alsa_card *card,
GtkWidget *top,
int *x
) {
int orient = card->has_speaker_switching
? GTK_ORIENTATION_HORIZONTAL
: GTK_ORIENTATION_VERTICAL;
GtkWidget *global_controls = create_global_box(top, x, orient);
GtkWidget *left = global_controls;
GtkWidget *right = 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);
}
add_clock_source_control(card, left);
add_sync_status_control(card, right);
add_speaker_switching_controls(card, left);
add_talkback_controls(card, right);
}
static GtkWidget *create_main_window_controls(struct alsa_card *card) {
int x = 0;
GtkWidget *top = gtk_grid_new();
gtk_widget_set_margin(top, 10);
gtk_grid_set_spacing(GTK_GRID(top), 10);
create_global_controls(card, top, &x);
create_input_controls(card, top, &x);
if (card->has_speaker_switching) {
x = 0;
GtkWidget *sep = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL);
gtk_grid_attach(GTK_GRID(top), sep, 0, 3, 3, 1);
create_output_controls(card, top, &x, 4, 3);
} else {
create_output_controls(card, top, &x, 0, 1);
}
return top;
}
static gboolean window_routing_close_request(GtkWindow *w, gpointer data) {
struct alsa_card *card = data;
gtk_widget_activate_action(
GTK_WIDGET(card->window_main), "win.routing", NULL
);
return true;
}
static gboolean window_mixer_close_request(GtkWindow *w, gpointer data) {
struct alsa_card *card = data;
gtk_widget_activate_action(
GTK_WIDGET(card->window_main), "win.mixer", NULL
);
return true;
}
static gboolean window_levels_close_request(GtkWindow *w, gpointer data) {
struct alsa_card *card = data;
gtk_widget_activate_action(
GTK_WIDGET(card->window_main), "win.levels", NULL
);
return true;
}
GtkWidget *create_iface_mixer_main(struct alsa_card *card) {
card->has_speaker_switching =
!!get_elem_by_name(card->elems, "Speaker Switching Playback Enum");
card->has_talkback =
!!get_elem_by_name(card->elems, "Talkback Playback Enum");
GtkWidget *top = create_main_window_controls(card);
GtkWidget *routing_top = create_routing_controls(card);
if (!routing_top)
return NULL;
card->window_routing = create_subwindow(
card, "Routing", G_CALLBACK(window_routing_close_request)
);
gtk_window_set_child(GTK_WINDOW(card->window_routing), routing_top);
GtkWidget *mixer_top = create_mixer_controls(card);
card->window_mixer = create_subwindow(
card, "Mixer", G_CALLBACK(window_mixer_close_request)
);
gtk_window_set_child(GTK_WINDOW(card->window_mixer), mixer_top);
GtkWidget *levels_top = create_levels_controls(card);
card->window_levels = create_subwindow(
card, "Levels", G_CALLBACK(window_levels_close_request)
);
gtk_window_set_child(GTK_WINDOW(card->window_levels), levels_top);
card->window_startup = create_subwindow(
card, "Startup Configuration", G_CALLBACK(window_startup_close_request)
);
GtkWidget *startup = create_startup_controls(card);
gtk_window_set_child(GTK_WINDOW(card->window_startup), startup);
return top;
}