Update gtkdial to support linear-volume controls

This commit is contained in:
Geoffrey D. Bennett
2024-12-27 07:45:51 +10:30
parent 0f7389dca8
commit 4a40b00695
4 changed files with 208 additions and 25 deletions

83
src/db.c Normal file
View File

@@ -0,0 +1,83 @@
// SPDX-FileCopyrightText: 2024 Geoffrey D. Bennett <g@b4.vu>
// SPDX-License-Identifier: GPL-3.0-or-later
#include <alsa/asoundlib.h>
#include <math.h>
static double db_to_linear(double db) {
if (db <= SND_CTL_TLV_DB_GAIN_MUTE)
return 0.0;
return pow(10.0, db / 20.0);
}
static double linear_to_db(double linear) {
if (linear <= 0.0)
return SND_CTL_TLV_DB_GAIN_MUTE;
return 20.0 * log10(linear);
}
int cdb_to_linear_value(
int cdb, int min_val, int max_val, int min_cdb, int max_cdb
) {
if (cdb <= min_cdb)
return min_val;
if (cdb >= max_cdb)
return max_val;
// Convert centidB to dB
double db = (double)cdb / 100.0;
double max_db = (double)max_cdb / 100.0;
// Convert dB relative to max_db to linear scale 0.0-1.0
double linear = db_to_linear(db - max_db);
// Scale to full ALSA range
double scaled = linear * (double)max_val;
int value = (int)round(scaled);
if (value < min_val)
return min_val;
if (value > max_val)
return max_val;
return value;
}
int linear_value_to_cdb(
int value, int min_val, int max_val, int min_cdb, int max_cdb
) {
if (value <= min_val)
return min_cdb;
if (value >= max_val)
return max_cdb;
// Convert to 0.0-1.0 linear scale
double linear = (double)value / (double)max_val;
double max_db = (double)max_cdb / 100.0;
// Convert to dB relative to max_db and back to centidB
int cdb = (int)round((linear_to_db(linear) + max_db) * 100.0);
if (cdb < min_cdb)
return min_cdb;
if (cdb > max_cdb)
return max_cdb;
return cdb;
}
double linear_value_to_db(
int value, int min_val, int max_val, int min_db, int max_db
) {
if (value <= min_val)
return min_db;
if (value >= max_val)
return max_db;
// Convert to 0.0-1.0 linear scale
double linear = (double)value / (double)max_val;
// Convert to dB relative to max_db
double db = linear_to_db(linear) + max_db;
if (db < min_db)
return min_db;
if (db > max_db)
return max_db;
return db;
}

16
src/db.h Normal file
View File

@@ -0,0 +1,16 @@
// SPDX-FileCopyrightText: 2024 Geoffrey D. Bennett <g@b4.vu>
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
int cdb_to_linear_value(
int cdb, int min_val, int max_val, int min_cdb, int max_cdb
);
int linear_value_to_cdb(
int value, int min_val, int max_val, int min_cdb, int max_cdb
);
double linear_value_to_db(
int value, int min_val, int max_val, int min_db, int max_db
);

View File

@@ -17,6 +17,7 @@
#include <math.h>
#include "gtkdial.h"
#include "db.h"
#define DIAL_MIN_WIDTH 50
#define DIAL_MAX_WIDTH 70
@@ -90,6 +91,7 @@ enum {
PROP_ROUND_DIGITS,
PROP_ZERO_DB,
PROP_OFF_DB,
PROP_IS_LINEAR,
PROP_TAPER,
PROP_CAN_CONTROL,
PROP_PEAK_HOLD,
@@ -118,6 +120,7 @@ struct _GtkDial {
int round_digits;
double zero_db;
double off_db;
gboolean is_linear;
int taper;
gboolean can_control;
int peak_hold;
@@ -254,6 +257,13 @@ static double calc_taper(GtkDial *dial, double val) {
double mn = gtk_adjustment_get_lower(dial->adj);
double mx = gtk_adjustment_get_upper(dial->adj);
double off_db = gtk_dial_get_off_db(dial);
gboolean is_linear = gtk_dial_get_is_linear(dial);
if (is_linear) {
val = linear_value_to_cdb(val, mn, mx, -8000, 1200) / 100.0;
mn = -60;
mx = 12;
}
// if off_db is set, then values below it are considered as
// almost-silence, so we clamp them to 0.01
@@ -551,6 +561,19 @@ static void gtk_dial_class_init(GtkDialClass *klass) {
G_PARAM_READWRITE | G_PARAM_CONSTRUCT
);
/**
* GtkDial:is_linear: (attributes org.gtk.Method.get=gtk_dial_get_is_linear org.gtk.Method.set=gtk_dial_set_is_linear)
*
* Whether the dial values are linear or dB.
*/
properties[PROP_IS_LINEAR] = g_param_spec_boolean(
"is_linear",
"IsLinear",
"Whether the dial values are linear or dB",
FALSE,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT
);
/**
* GtkDial:taper: (attributes org.gtk.Method.get=gtk_dial_get_taper org.gtk.Method.set=gtk_dial_set_taper)
*
@@ -1037,6 +1060,9 @@ static void gtk_dial_set_property(
case PROP_OFF_DB:
gtk_dial_set_off_db(dial, g_value_get_double(value));
break;
case PROP_IS_LINEAR:
gtk_dial_set_is_linear(dial, g_value_get_boolean(value));
break;
case PROP_TAPER:
gtk_dial_set_taper(dial, g_value_get_int(value));
break;
@@ -1073,6 +1099,9 @@ static void gtk_dial_get_property(
case PROP_OFF_DB:
g_value_set_double(value, dial->off_db);
break;
case PROP_IS_LINEAR:
g_value_set_boolean(value, dial->is_linear);
break;
case PROP_TAPER:
g_value_set_int(value, dial->taper);
break;
@@ -1124,6 +1153,15 @@ double gtk_dial_get_off_db(GtkDial *dial) {
return dial->off_db;
}
void gtk_dial_set_is_linear(GtkDial *dial, gboolean is_linear) {
dial->is_linear = is_linear;
dial->properties_updated = 1;
}
gboolean gtk_dial_get_is_linear(GtkDial *dial) {
return dial->is_linear;
}
void gtk_dial_set_taper(GtkDial *dial, int taper) {
dial->taper = taper;
dial->properties_updated = 1;
@@ -1288,32 +1326,41 @@ static int set_value(GtkDial *dial, double newval) {
return old_valp != dial->valp || old_peak != dial->current_peak;
}
static void step_back(GtkDial *dial) {
double newval;
static double do_step(GtkDial *dial, double step_amount) {
double mn = gtk_adjustment_get_lower(dial->adj);
double mx = gtk_adjustment_get_upper(dial->adj);
double newval = gtk_adjustment_get_value(dial->adj);
double step = gtk_adjustment_get_step_increment(dial->adj);
newval = gtk_adjustment_get_value(dial->adj) - gtk_adjustment_get_step_increment(dial->adj);
set_value(dial, newval);
if (gtk_dial_get_is_linear(dial)) {
double db_val = linear_value_to_cdb(newval, mn, mx, -8000, 1200) / 100.0;
db_val = round(db_val / step) * step + step_amount;
newval = cdb_to_linear_value(db_val * 100.0, mn, mx, -8000, 1200);
if (newval == gtk_adjustment_get_value(dial->adj)) {
newval = CLAMP(newval + (step_amount > 0 ? 1 : -1), mn, mx);
}
} else {
newval += step_amount;
}
return newval;
}
static void step_back(GtkDial *dial) {
set_value(dial, do_step(dial, -gtk_adjustment_get_step_increment(dial->adj)));
}
static void step_forward(GtkDial *dial) {
double newval;
newval = gtk_adjustment_get_value(dial->adj) + gtk_adjustment_get_step_increment(dial->adj);
set_value(dial, newval);
set_value(dial, do_step(dial, gtk_adjustment_get_step_increment(dial->adj)));
}
static void page_back(GtkDial *dial) {
double newval;
newval = gtk_adjustment_get_value(dial->adj) - gtk_adjustment_get_page_increment(dial->adj);
set_value(dial, newval);
set_value(dial, do_step(dial, -gtk_adjustment_get_page_increment(dial->adj)));
}
static void page_forward(GtkDial *dial) {
double newval;
newval = gtk_adjustment_get_value(dial->adj) + gtk_adjustment_get_page_increment(dial->adj);
set_value(dial, newval);
set_value(dial, do_step(dial, gtk_adjustment_get_page_increment(dial->adj)));
}
static void scroll_begin(GtkDial *dial) {
@@ -1447,16 +1494,43 @@ static void gtk_dial_drag_gesture_update(
gtk_gesture_drag_get_start_point(gesture, &start_x, &start_y);
double valp = dial->dvalp - DRAG_FACTOR * (offset_y / dial->h);
valp = CLAMP(valp, 0.0, 1.0);
double mn = gtk_adjustment_get_lower(dial->adj);
double mx = gtk_adjustment_get_upper(dial->adj);
gboolean is_linear = gtk_dial_get_is_linear(dial);
double val = calc_val(
valp,
gtk_adjustment_get_lower(dial->adj),
gtk_adjustment_get_upper(dial->adj)
);
double valp;
if (is_linear) {
double step = gtk_adjustment_get_step_increment(dial->adj);
// Convert initial value from linear to dB space
double db_val = linear_value_to_cdb(
calc_val(dial->dvalp, mn, mx),
mn, mx,
-8000, 1200
) / 100.0;
// Adjust in dB space
db_val -= 30.0 * DRAG_FACTOR * (offset_y / dial->h);
// Round
db_val = round(db_val / step) * step;
// Convert back to linear space and normalise
double val = cdb_to_linear_value(
db_val * 100.0,
mn, mx,
-8000, 1200
);
valp = calc_valp(val, mn, mx);
} else {
valp = dial->dvalp - DRAG_FACTOR * (offset_y / dial->h);
valp = CLAMP(valp, 0.0, 1.0);
}
set_value(dial, calc_val(valp, mn, mx));
set_value(dial, val);
gtk_widget_queue_draw(GTK_WIDGET(dial));
}
@@ -1519,7 +1593,7 @@ static gboolean gtk_dial_scroll_controller_scroll(
double step = -gtk_adjustment_get_step_increment(dial->adj) * delta;
set_value(dial, gtk_adjustment_get_value(dial->adj) + step);
set_value(dial, do_step(dial, step));
gtk_widget_queue_draw(GTK_WIDGET(dial));
return GDK_EVENT_STOP;

View File

@@ -68,6 +68,9 @@ double gtk_dial_get_zero_db(GtkDial *dial);
void gtk_dial_set_off_db(GtkDial *dial, double off_db);
double gtk_dial_get_off_db(GtkDial *dial);
void gtk_dial_set_is_linear(GtkDial *dial, gboolean is_linear);
gboolean gtk_dial_get_is_linear(GtkDial *dial);
// taper functions
enum {
GTK_DIAL_TAPER_LINEAR,
@@ -98,6 +101,13 @@ void gtk_dial_set_peak_hold(GtkDial *dial, int peak_hold);
int gtk_dial_get_peak_hold(GtkDial *dial);
void gtk_dial_peak_tick(void);
int cdb_to_linear_value(
int db, int min_val, int max_val, int min_db, int max_db
);
int linear_value_to_cdb(
int value, int min_val, int max_val, int min_db, int max_db
);
G_END_DECLS
#endif