Add support for piecewise linear interpolation taper to GtkDial

This commit adds support for piecewise linear interpolation tapers to
GtkDial and the gain widget so that the 4th Gen 4i4 volume knob taper
can be modelled correctly.
This commit is contained in:
Geoffrey D. Bennett
2024-01-17 17:54:11 +10:30
parent 492c348897
commit df5d0960dd
5 changed files with 93 additions and 4 deletions

View File

@@ -119,6 +119,11 @@ struct _GtkDial {
int round_digits;
double zero_db;
int taper;
// linear taper breakpoints array
double *taper_breakpoints;
double *taper_outputs;
int taper_breakpoints_count;
};
G_DEFINE_TYPE(GtkDial, gtk_dial, GTK_TYPE_WIDGET)
@@ -159,6 +164,24 @@ static double calc_valp(double val, double mn, double mx) {
return (val - mn) / (mx - mn);
}
static double taper_linear(double val, double *bp, double *out, int count) {
if (count < 3)
return val;
if (val <= bp[0])
return out[0];
for (int i = 0; i < count - 1; i++) {
if (val > bp[i + 1])
continue;
double scale = (out[i + 1] - out[i]) / (bp[i + 1] - bp[i]);
return out[i] + scale * (val - bp[i]);
}
return out[count - 1];
}
static double taper_log(double val) {
// 10^(val - 1) converts it to 0.1..1 with a nice curve
@@ -170,7 +193,12 @@ static double taper_log(double val) {
static double calc_taper(GtkDial *dial, double val) {
if (dial->taper == GTK_DIAL_TAPER_LINEAR)
return val;
return taper_linear(
val,
dial->taper_breakpoints,
dial->taper_outputs,
dial->taper_breakpoints_count
);
if (dial->taper == GTK_DIAL_TAPER_LOG)
return taper_log(val);
@@ -623,6 +651,40 @@ int gtk_dial_get_taper(GtkDial *dial) {
return dial->taper;
}
void gtk_dial_set_taper_linear_breakpoints(
GtkDial *dial,
const double *breakpoints,
const double *outputs,
int count
) {
free(dial->taper_breakpoints);
free(dial->taper_outputs);
dial->taper_breakpoints = NULL;
dial->taper_outputs = NULL;
dial->taper_breakpoints_count = 0;
if (count < 1)
return;
int total_count = count + 2;
dial->taper_breakpoints = malloc(total_count * sizeof(double));
dial->taper_outputs = malloc(total_count * sizeof(double));
dial->taper_breakpoints[0] = 0;
dial->taper_outputs[0] = 0;
for (int i = 0; i < count; i++) {
dial->taper_breakpoints[i + 1] = breakpoints[i];
dial->taper_outputs[i + 1] = outputs[i];
}
dial->taper_breakpoints[total_count - 1] = 1;
dial->taper_outputs[total_count - 1] = 1;
dial->taper_breakpoints_count = total_count;
}
gboolean gtk_dial_set_style(
GtkDial *dial,
const char *trough_border,
@@ -919,6 +981,13 @@ static gboolean gtk_dial_scroll_controller_scroll(
void gtk_dial_dispose(GObject *o) {
GtkDial *dial = GTK_DIAL(o);
free(dial->taper_breakpoints);
dial->taper_breakpoints = NULL;
free(dial->taper_outputs);
dial->taper_outputs = NULL;
dial->taper_breakpoints_count = 0;
g_object_unref(dial->adj);
dial->adj = NULL;
G_OBJECT_CLASS(gtk_dial_parent_class)->dispose(o);

View File

@@ -74,6 +74,13 @@ enum {
void gtk_dial_set_taper(GtkDial *dial, int taper);
int gtk_dial_get_taper(GtkDial *dial);
void gtk_dial_set_taper_linear_breakpoints(
GtkDial *dial,
const double *breakpoints,
const double *outputs,
int count
);
/**
* @brief Set the colors which this dial uses. String codes can be one of the following:
* A standard name (Taken from the X11 rgb.txt file)

View File

@@ -503,7 +503,11 @@ static void create_output_controls(
GtkWidget *l = gtk_label_new(gen4 ? "Line 12" : "HW");
gtk_grid_attach(GTK_GRID(output_grid), l, 0, 0, 1, 1);
w = make_gain_alsa_elem(elem, 1, WIDGET_GAIN_TAPER_LOG);
if (gen4) {
w = make_gain_alsa_elem(elem, 1, WIDGET_GAIN_TAPER_GEN4_VOLUME);
} else {
w = make_gain_alsa_elem(elem, 1, WIDGET_GAIN_TAPER_LOG);
}
gtk_widget_set_tooltip_text(
w,
gen4
@@ -522,7 +526,7 @@ static void create_output_controls(
"This control shows the setting of the headphone volume knob."
);
gtk_grid_attach(GTK_GRID(output_grid), l, 1, 0, 1, 1);
w = make_gain_alsa_elem(elem, 1, WIDGET_GAIN_TAPER_LOG);
w = make_gain_alsa_elem(elem, 1, WIDGET_GAIN_TAPER_GEN4_VOLUME);
gtk_grid_attach(GTK_GRID(output_grid), w, 1, 1, 1, 1);
} else if (strcmp(elem->name, "Mute Playback Switch") == 0) {
w = make_boolean_alsa_elem(

View File

@@ -92,6 +92,14 @@ GtkWidget *make_gain_alsa_elem(
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
);
data->label = gtk_label_new(NULL);
gtk_widget_set_vexpand(data->dial, TRUE);

View File

@@ -9,7 +9,8 @@
enum {
WIDGET_GAIN_TAPER_LINEAR,
WIDGET_GAIN_TAPER_LOG
WIDGET_GAIN_TAPER_LOG,
WIDGET_GAIN_TAPER_GEN4_VOLUME
};
GtkWidget *make_gain_alsa_elem(