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".
471 lines
14 KiB
C
471 lines
14 KiB
C
// 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 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;
|
||
}
|