// SPDX-FileCopyrightText: 2022 Geoffrey D. Bennett // 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 aren’t 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 aren’t 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; }