Update gtkdial to support linear-volume controls
This commit is contained in:
83
src/db.c
Normal file
83
src/db.c
Normal 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
16
src/db.h
Normal 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
|
||||||
|
);
|
||||||
124
src/gtkdial.c
124
src/gtkdial.c
@@ -17,6 +17,7 @@
|
|||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
|
||||||
#include "gtkdial.h"
|
#include "gtkdial.h"
|
||||||
|
#include "db.h"
|
||||||
|
|
||||||
#define DIAL_MIN_WIDTH 50
|
#define DIAL_MIN_WIDTH 50
|
||||||
#define DIAL_MAX_WIDTH 70
|
#define DIAL_MAX_WIDTH 70
|
||||||
@@ -90,6 +91,7 @@ enum {
|
|||||||
PROP_ROUND_DIGITS,
|
PROP_ROUND_DIGITS,
|
||||||
PROP_ZERO_DB,
|
PROP_ZERO_DB,
|
||||||
PROP_OFF_DB,
|
PROP_OFF_DB,
|
||||||
|
PROP_IS_LINEAR,
|
||||||
PROP_TAPER,
|
PROP_TAPER,
|
||||||
PROP_CAN_CONTROL,
|
PROP_CAN_CONTROL,
|
||||||
PROP_PEAK_HOLD,
|
PROP_PEAK_HOLD,
|
||||||
@@ -118,6 +120,7 @@ struct _GtkDial {
|
|||||||
int round_digits;
|
int round_digits;
|
||||||
double zero_db;
|
double zero_db;
|
||||||
double off_db;
|
double off_db;
|
||||||
|
gboolean is_linear;
|
||||||
int taper;
|
int taper;
|
||||||
gboolean can_control;
|
gboolean can_control;
|
||||||
int peak_hold;
|
int peak_hold;
|
||||||
@@ -254,6 +257,13 @@ static double calc_taper(GtkDial *dial, double val) {
|
|||||||
double mn = gtk_adjustment_get_lower(dial->adj);
|
double mn = gtk_adjustment_get_lower(dial->adj);
|
||||||
double mx = gtk_adjustment_get_upper(dial->adj);
|
double mx = gtk_adjustment_get_upper(dial->adj);
|
||||||
double off_db = gtk_dial_get_off_db(dial);
|
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
|
// if off_db is set, then values below it are considered as
|
||||||
// almost-silence, so we clamp them to 0.01
|
// 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
|
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)
|
* 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:
|
case PROP_OFF_DB:
|
||||||
gtk_dial_set_off_db(dial, g_value_get_double(value));
|
gtk_dial_set_off_db(dial, g_value_get_double(value));
|
||||||
break;
|
break;
|
||||||
|
case PROP_IS_LINEAR:
|
||||||
|
gtk_dial_set_is_linear(dial, g_value_get_boolean(value));
|
||||||
|
break;
|
||||||
case PROP_TAPER:
|
case PROP_TAPER:
|
||||||
gtk_dial_set_taper(dial, g_value_get_int(value));
|
gtk_dial_set_taper(dial, g_value_get_int(value));
|
||||||
break;
|
break;
|
||||||
@@ -1073,6 +1099,9 @@ static void gtk_dial_get_property(
|
|||||||
case PROP_OFF_DB:
|
case PROP_OFF_DB:
|
||||||
g_value_set_double(value, dial->off_db);
|
g_value_set_double(value, dial->off_db);
|
||||||
break;
|
break;
|
||||||
|
case PROP_IS_LINEAR:
|
||||||
|
g_value_set_boolean(value, dial->is_linear);
|
||||||
|
break;
|
||||||
case PROP_TAPER:
|
case PROP_TAPER:
|
||||||
g_value_set_int(value, dial->taper);
|
g_value_set_int(value, dial->taper);
|
||||||
break;
|
break;
|
||||||
@@ -1124,6 +1153,15 @@ double gtk_dial_get_off_db(GtkDial *dial) {
|
|||||||
return dial->off_db;
|
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) {
|
void gtk_dial_set_taper(GtkDial *dial, int taper) {
|
||||||
dial->taper = taper;
|
dial->taper = taper;
|
||||||
dial->properties_updated = 1;
|
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;
|
return old_valp != dial->valp || old_peak != dial->current_peak;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void step_back(GtkDial *dial) {
|
static double do_step(GtkDial *dial, double step_amount) {
|
||||||
double newval;
|
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);
|
if (gtk_dial_get_is_linear(dial)) {
|
||||||
set_value(dial, newval);
|
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) {
|
static void step_forward(GtkDial *dial) {
|
||||||
double newval;
|
set_value(dial, do_step(dial, 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void page_back(GtkDial *dial) {
|
static void page_back(GtkDial *dial) {
|
||||||
double newval;
|
set_value(dial, do_step(dial, -gtk_adjustment_get_page_increment(dial->adj)));
|
||||||
|
|
||||||
newval = gtk_adjustment_get_value(dial->adj) - gtk_adjustment_get_page_increment(dial->adj);
|
|
||||||
set_value(dial, newval);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void page_forward(GtkDial *dial) {
|
static void page_forward(GtkDial *dial) {
|
||||||
double newval;
|
set_value(dial, do_step(dial, gtk_adjustment_get_page_increment(dial->adj)));
|
||||||
|
|
||||||
newval = gtk_adjustment_get_value(dial->adj) + gtk_adjustment_get_page_increment(dial->adj);
|
|
||||||
set_value(dial, newval);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void scroll_begin(GtkDial *dial) {
|
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);
|
gtk_gesture_drag_get_start_point(gesture, &start_x, &start_y);
|
||||||
|
|
||||||
double valp = dial->dvalp - DRAG_FACTOR * (offset_y / dial->h);
|
double mn = gtk_adjustment_get_lower(dial->adj);
|
||||||
valp = CLAMP(valp, 0.0, 1.0);
|
double mx = gtk_adjustment_get_upper(dial->adj);
|
||||||
|
gboolean is_linear = gtk_dial_get_is_linear(dial);
|
||||||
|
|
||||||
double val = calc_val(
|
double valp;
|
||||||
valp,
|
|
||||||
gtk_adjustment_get_lower(dial->adj),
|
if (is_linear) {
|
||||||
gtk_adjustment_get_upper(dial->adj)
|
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));
|
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;
|
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));
|
gtk_widget_queue_draw(GTK_WIDGET(dial));
|
||||||
|
|
||||||
return GDK_EVENT_STOP;
|
return GDK_EVENT_STOP;
|
||||||
|
|||||||
@@ -68,6 +68,9 @@ double gtk_dial_get_zero_db(GtkDial *dial);
|
|||||||
void gtk_dial_set_off_db(GtkDial *dial, double off_db);
|
void gtk_dial_set_off_db(GtkDial *dial, double off_db);
|
||||||
double gtk_dial_get_off_db(GtkDial *dial);
|
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
|
// taper functions
|
||||||
enum {
|
enum {
|
||||||
GTK_DIAL_TAPER_LINEAR,
|
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);
|
int gtk_dial_get_peak_hold(GtkDial *dial);
|
||||||
void gtk_dial_peak_tick(void);
|
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
|
G_END_DECLS
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
Reference in New Issue
Block a user