Add tabbed Whisper settings page with persisted decode params and live runtime updates

This commit is contained in:
Laan Tungir
2026-03-26 21:51:02 -04:00
parent 4dbd14f423
commit e83da30048
6 changed files with 780 additions and 41 deletions
+3 -2
View File
@@ -2,7 +2,7 @@
# Hotkey format supports Ctrl, Alt, Shift, Super + final key symbol
# Example: Ctrl+Alt+V
model_path=./models/ggml-base.en.bin
model_path=./models/ggml-medium.en.bin
use_gpu=1
audio_device=alsa_input.usb-Shure_Inc_Shure_MV6_MV6_5-ed2d37fa14d3e65d8d477cacf910d93c-01.mono-fallback
sample_rate=16000
@@ -16,7 +16,7 @@ always_on=1
always_on_window_ms=1400
# live level threshold (0.0 - 1.0)
vad_peak_threshold=0.006
vad_peak_threshold=0.0060
# level must stay above threshold this long before speech starts
vad_trigger_ms=100
# level must stay below threshold this long before speech ends
@@ -29,3 +29,4 @@ vad_min_speech_ms=250
vad_max_speech_ms=15000
autotype_enabled=1
filter_bracketed_tags=1
+169
View File
@@ -39,6 +39,43 @@ void config_set_defaults(voice_config_t *cfg) {
cfg->vad_max_speech_ms = 15000;
cfg->autotype_enabled = 1;
cfg->filter_bracketed_tags = 1;
cfg->whisper_sampling_strategy = 0;
cfg->whisper_n_max_text_ctx = 16384;
cfg->whisper_offset_ms = 0;
cfg->whisper_duration_ms = 0;
cfg->whisper_translate = 0;
cfg->whisper_detect_language = 0;
cfg->whisper_no_context = 0;
cfg->whisper_no_timestamps = 1;
cfg->whisper_single_segment = 0;
cfg->whisper_token_timestamps = 0;
cfg->whisper_thold_pt = 0.01f;
cfg->whisper_thold_ptsum = 0.01f;
cfg->whisper_max_len = 0;
cfg->whisper_split_on_word = 0;
cfg->whisper_max_tokens = 0;
cfg->whisper_audio_ctx = 0;
cfg->whisper_tdrz_enable = 0;
cfg->whisper_suppress_blank = 1;
cfg->whisper_suppress_nst = 0;
cfg->whisper_temperature = 0.0f;
cfg->whisper_max_initial_ts = 1.0f;
cfg->whisper_length_penalty = -1.0f;
cfg->whisper_temperature_inc = 0.2f;
cfg->whisper_entropy_thold = 2.4f;
cfg->whisper_logprob_thold = -1.0f;
cfg->whisper_no_speech_thold = 0.6f;
cfg->whisper_greedy_best_of = 1;
cfg->whisper_beam_size = 5;
cfg->whisper_beam_patience = -1.0f;
}
int config_load_file(const char *path, voice_config_t *cfg) {
@@ -97,9 +134,141 @@ int config_load_file(const char *path, voice_config_t *cfg) {
cfg->autotype_enabled = atoi(value);
} else if (strcmp(key, "filter_bracketed_tags") == 0) {
cfg->filter_bracketed_tags = atoi(value);
} else if (strcmp(key, "whisper_sampling_strategy") == 0) {
cfg->whisper_sampling_strategy = atoi(value);
} else if (strcmp(key, "whisper_n_max_text_ctx") == 0) {
cfg->whisper_n_max_text_ctx = atoi(value);
} else if (strcmp(key, "whisper_offset_ms") == 0) {
cfg->whisper_offset_ms = atoi(value);
} else if (strcmp(key, "whisper_duration_ms") == 0) {
cfg->whisper_duration_ms = atoi(value);
} else if (strcmp(key, "whisper_translate") == 0) {
cfg->whisper_translate = atoi(value);
} else if (strcmp(key, "whisper_detect_language") == 0) {
cfg->whisper_detect_language = atoi(value);
} else if (strcmp(key, "whisper_no_context") == 0) {
cfg->whisper_no_context = atoi(value);
} else if (strcmp(key, "whisper_no_timestamps") == 0) {
cfg->whisper_no_timestamps = atoi(value);
} else if (strcmp(key, "whisper_single_segment") == 0) {
cfg->whisper_single_segment = atoi(value);
} else if (strcmp(key, "whisper_token_timestamps") == 0) {
cfg->whisper_token_timestamps = atoi(value);
} else if (strcmp(key, "whisper_thold_pt") == 0) {
cfg->whisper_thold_pt = (float)atof(value);
} else if (strcmp(key, "whisper_thold_ptsum") == 0) {
cfg->whisper_thold_ptsum = (float)atof(value);
} else if (strcmp(key, "whisper_max_len") == 0) {
cfg->whisper_max_len = atoi(value);
} else if (strcmp(key, "whisper_split_on_word") == 0) {
cfg->whisper_split_on_word = atoi(value);
} else if (strcmp(key, "whisper_max_tokens") == 0) {
cfg->whisper_max_tokens = atoi(value);
} else if (strcmp(key, "whisper_audio_ctx") == 0) {
cfg->whisper_audio_ctx = atoi(value);
} else if (strcmp(key, "whisper_tdrz_enable") == 0) {
cfg->whisper_tdrz_enable = atoi(value);
} else if (strcmp(key, "whisper_suppress_blank") == 0) {
cfg->whisper_suppress_blank = atoi(value);
} else if (strcmp(key, "whisper_suppress_nst") == 0) {
cfg->whisper_suppress_nst = atoi(value);
} else if (strcmp(key, "whisper_temperature") == 0) {
cfg->whisper_temperature = (float)atof(value);
} else if (strcmp(key, "whisper_max_initial_ts") == 0) {
cfg->whisper_max_initial_ts = (float)atof(value);
} else if (strcmp(key, "whisper_length_penalty") == 0) {
cfg->whisper_length_penalty = (float)atof(value);
} else if (strcmp(key, "whisper_temperature_inc") == 0) {
cfg->whisper_temperature_inc = (float)atof(value);
} else if (strcmp(key, "whisper_entropy_thold") == 0) {
cfg->whisper_entropy_thold = (float)atof(value);
} else if (strcmp(key, "whisper_logprob_thold") == 0) {
cfg->whisper_logprob_thold = (float)atof(value);
} else if (strcmp(key, "whisper_no_speech_thold") == 0) {
cfg->whisper_no_speech_thold = (float)atof(value);
} else if (strcmp(key, "whisper_greedy_best_of") == 0) {
cfg->whisper_greedy_best_of = atoi(value);
} else if (strcmp(key, "whisper_beam_size") == 0) {
cfg->whisper_beam_size = atoi(value);
} else if (strcmp(key, "whisper_beam_patience") == 0) {
cfg->whisper_beam_patience = (float)atof(value);
}
}
fclose(f);
return 0;
}
int config_save_file(const char *path, const voice_config_t *cfg) {
if (!path || !cfg) return -1;
FILE *f = fopen(path, "w");
if (!f) return -2;
fprintf(f, "# voice_linux runtime configuration\n");
fprintf(f, "# Hotkey format supports Ctrl, Alt, Shift, Super + final key symbol\n");
fprintf(f, "# Example: Ctrl+Alt+V\n\n");
fprintf(f, "model_path=%s\n", cfg->model_path);
fprintf(f, "use_gpu=%d\n", cfg->use_gpu ? 1 : 0);
fprintf(f, "audio_device=%s\n", cfg->audio_device);
fprintf(f, "sample_rate=%d\n", cfg->sample_rate);
fprintf(f, "language=%s\n", cfg->language);
fprintf(f, "hotkey=%s\n", cfg->hotkey);
fprintf(f, "type_delay_us=%d\n", cfg->type_delay_us);
fprintf(f, "max_record_seconds=%d\n", cfg->max_record_seconds);
fprintf(f, "# always-on VAD controls\n");
fprintf(f, "always_on=%d\n", cfg->always_on ? 1 : 0);
fprintf(f, "# legacy window size (unused by new VAD pipeline, kept for compatibility)\n");
fprintf(f, "always_on_window_ms=%d\n\n", cfg->always_on_window_ms);
fprintf(f, "# live level threshold (0.0 - 1.0)\n");
fprintf(f, "vad_peak_threshold=%.4f\n", cfg->vad_peak_threshold);
fprintf(f, "# level must stay above threshold this long before speech starts\n");
fprintf(f, "vad_trigger_ms=%d\n", cfg->vad_trigger_ms);
fprintf(f, "# level must stay below threshold this long before speech ends\n");
fprintf(f, "vad_release_ms=%d\n", cfg->vad_release_ms);
fprintf(f, "# include this much pre-trigger audio in transcript segment\n");
fprintf(f, "vad_preroll_ms=%d\n", cfg->vad_preroll_ms);
fprintf(f, "# discard segments shorter than this\n");
fprintf(f, "vad_min_speech_ms=%d\n", cfg->vad_min_speech_ms);
fprintf(f, "# hard cap for one speech segment\n");
fprintf(f, "vad_max_speech_ms=%d\n\n", cfg->vad_max_speech_ms);
fprintf(f, "autotype_enabled=%d\n", cfg->autotype_enabled ? 1 : 0);
fprintf(f, "filter_bracketed_tags=%d\n\n", cfg->filter_bracketed_tags ? 1 : 0);
fprintf(f, "# whisper decode controls\n");
fprintf(f, "whisper_sampling_strategy=%d\n", cfg->whisper_sampling_strategy);
fprintf(f, "whisper_n_max_text_ctx=%d\n", cfg->whisper_n_max_text_ctx);
fprintf(f, "whisper_offset_ms=%d\n", cfg->whisper_offset_ms);
fprintf(f, "whisper_duration_ms=%d\n", cfg->whisper_duration_ms);
fprintf(f, "whisper_translate=%d\n", cfg->whisper_translate ? 1 : 0);
fprintf(f, "whisper_detect_language=%d\n", cfg->whisper_detect_language ? 1 : 0);
fprintf(f, "whisper_no_context=%d\n", cfg->whisper_no_context ? 1 : 0);
fprintf(f, "whisper_no_timestamps=%d\n", cfg->whisper_no_timestamps ? 1 : 0);
fprintf(f, "whisper_single_segment=%d\n", cfg->whisper_single_segment ? 1 : 0);
fprintf(f, "whisper_token_timestamps=%d\n", cfg->whisper_token_timestamps ? 1 : 0);
fprintf(f, "whisper_thold_pt=%.4f\n", cfg->whisper_thold_pt);
fprintf(f, "whisper_thold_ptsum=%.4f\n", cfg->whisper_thold_ptsum);
fprintf(f, "whisper_max_len=%d\n", cfg->whisper_max_len);
fprintf(f, "whisper_split_on_word=%d\n", cfg->whisper_split_on_word ? 1 : 0);
fprintf(f, "whisper_max_tokens=%d\n", cfg->whisper_max_tokens);
fprintf(f, "whisper_audio_ctx=%d\n", cfg->whisper_audio_ctx);
fprintf(f, "whisper_tdrz_enable=%d\n", cfg->whisper_tdrz_enable ? 1 : 0);
fprintf(f, "whisper_suppress_blank=%d\n", cfg->whisper_suppress_blank ? 1 : 0);
fprintf(f, "whisper_suppress_nst=%d\n", cfg->whisper_suppress_nst ? 1 : 0);
fprintf(f, "whisper_temperature=%.4f\n", cfg->whisper_temperature);
fprintf(f, "whisper_max_initial_ts=%.4f\n", cfg->whisper_max_initial_ts);
fprintf(f, "whisper_length_penalty=%.4f\n", cfg->whisper_length_penalty);
fprintf(f, "whisper_temperature_inc=%.4f\n", cfg->whisper_temperature_inc);
fprintf(f, "whisper_entropy_thold=%.4f\n", cfg->whisper_entropy_thold);
fprintf(f, "whisper_logprob_thold=%.4f\n", cfg->whisper_logprob_thold);
fprintf(f, "whisper_no_speech_thold=%.4f\n", cfg->whisper_no_speech_thold);
fprintf(f, "whisper_greedy_best_of=%d\n", cfg->whisper_greedy_best_of);
fprintf(f, "whisper_beam_size=%d\n", cfg->whisper_beam_size);
fprintf(f, "whisper_beam_patience=%.4f\n", cfg->whisper_beam_patience);
fclose(f);
return 0;
}
+38
View File
@@ -22,9 +22,47 @@ typedef struct {
int vad_max_speech_ms;
int autotype_enabled;
int filter_bracketed_tags;
int whisper_sampling_strategy; // 0=greedy, 1=beam search
int whisper_n_max_text_ctx;
int whisper_offset_ms;
int whisper_duration_ms;
int whisper_translate;
int whisper_detect_language;
int whisper_no_context;
int whisper_no_timestamps;
int whisper_single_segment;
int whisper_token_timestamps;
float whisper_thold_pt;
float whisper_thold_ptsum;
int whisper_max_len;
int whisper_split_on_word;
int whisper_max_tokens;
int whisper_audio_ctx;
int whisper_tdrz_enable;
int whisper_suppress_blank;
int whisper_suppress_nst;
float whisper_temperature;
float whisper_max_initial_ts;
float whisper_length_penalty;
float whisper_temperature_inc;
float whisper_entropy_thold;
float whisper_logprob_thold;
float whisper_no_speech_thold;
int whisper_greedy_best_of;
int whisper_beam_size;
float whisper_beam_patience;
} voice_config_t;
void config_set_defaults(voice_config_t *cfg);
int config_load_file(const char *path, voice_config_t *cfg);
int config_save_file(const char *path, const voice_config_t *cfg);
#endif
+475 -35
View File
@@ -10,6 +10,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <time.h>
@@ -22,6 +23,7 @@ typedef struct {
GtkWidget *type_check;
GtkWidget *filter_tags_check;
GtkWidget *transcript_view;
GtkWidget *gpu_light;
GtkWidget *record_light;
GtkWidget *transcribe_light;
@@ -34,6 +36,38 @@ typedef struct {
GtkWidget *max_speech_scale;
GtkWidget *debug_label;
GtkWidget *sampling_combo;
GtkWidget *translate_check;
GtkWidget *detect_language_check;
GtkWidget *no_context_check;
GtkWidget *no_timestamps_check;
GtkWidget *single_segment_check;
GtkWidget *token_timestamps_check;
GtkWidget *split_on_word_check;
GtkWidget *suppress_blank_check;
GtkWidget *suppress_nst_check;
GtkWidget *tdrz_enable_check;
GtkWidget *n_max_text_ctx_spin;
GtkWidget *offset_ms_spin;
GtkWidget *duration_ms_spin;
GtkWidget *max_len_spin;
GtkWidget *max_tokens_spin;
GtkWidget *audio_ctx_spin;
GtkWidget *greedy_best_of_spin;
GtkWidget *beam_size_spin;
GtkWidget *thold_pt_spin;
GtkWidget *thold_ptsum_spin;
GtkWidget *temperature_spin;
GtkWidget *max_initial_ts_spin;
GtkWidget *length_penalty_spin;
GtkWidget *temperature_inc_spin;
GtkWidget *entropy_thold_spin;
GtkWidget *logprob_thold_spin;
GtkWidget *no_speech_thold_spin;
GtkWidget *beam_patience_spin;
voice_config_t cfg;
int use_gpu;
int recording;
@@ -45,6 +79,7 @@ typedef struct {
int runtime_ready;
char last_transcript[4096];
double last_transcript_time;
char cfg_path[512];
} gui_state_t;
static float clampf(float v, float lo, float hi) {
@@ -71,8 +106,74 @@ static void append_transcript(gui_state_t *s, const char *text) {
gtk_text_buffer_insert(buf, &end, text, -1);
}
static void save_config_best_effort(gui_state_t *s) {
if (!s || s->cfg_path[0] == '\0') return;
if (config_save_file(s->cfg_path, &s->cfg) != 0) {
fprintf(stderr, "gui: warning: failed to save config to %s\n", s->cfg_path);
}
}
static void fill_transcribe_params_from_cfg(const gui_state_t *s, transcribe_params_t *tp) {
if (!s || !tp) return;
memset(tp, 0, sizeof(*tp));
snprintf(tp->model_path, sizeof(tp->model_path), "%s", s->cfg.model_path);
snprintf(tp->language, sizeof(tp->language), "%s", s->cfg.language);
tp->use_gpu = s->use_gpu;
tp->n_threads = (int)sysconf(_SC_NPROCESSORS_ONLN);
tp->filter_bracketed_tags = s->cfg.filter_bracketed_tags;
tp->sampling_strategy = s->cfg.whisper_sampling_strategy;
tp->n_max_text_ctx = s->cfg.whisper_n_max_text_ctx;
tp->offset_ms = s->cfg.whisper_offset_ms;
tp->duration_ms = s->cfg.whisper_duration_ms;
tp->translate = s->cfg.whisper_translate;
tp->detect_language = s->cfg.whisper_detect_language;
tp->no_context = s->cfg.whisper_no_context;
tp->no_timestamps = s->cfg.whisper_no_timestamps;
tp->single_segment = s->cfg.whisper_single_segment;
tp->token_timestamps = s->cfg.whisper_token_timestamps;
tp->thold_pt = s->cfg.whisper_thold_pt;
tp->thold_ptsum = s->cfg.whisper_thold_ptsum;
tp->max_len = s->cfg.whisper_max_len;
tp->split_on_word = s->cfg.whisper_split_on_word;
tp->max_tokens = s->cfg.whisper_max_tokens;
tp->audio_ctx = s->cfg.whisper_audio_ctx;
tp->tdrz_enable = s->cfg.whisper_tdrz_enable;
tp->suppress_blank = s->cfg.whisper_suppress_blank;
tp->suppress_nst = s->cfg.whisper_suppress_nst;
tp->temperature = s->cfg.whisper_temperature;
tp->max_initial_ts = s->cfg.whisper_max_initial_ts;
tp->length_penalty = s->cfg.whisper_length_penalty;
tp->temperature_inc = s->cfg.whisper_temperature_inc;
tp->entropy_thold = s->cfg.whisper_entropy_thold;
tp->logprob_thold = s->cfg.whisper_logprob_thold;
tp->no_speech_thold = s->cfg.whisper_no_speech_thold;
tp->greedy_best_of = s->cfg.whisper_greedy_best_of;
tp->beam_size = s->cfg.whisper_beam_size;
tp->beam_patience = s->cfg.whisper_beam_patience;
}
static void push_transcribe_runtime_params(gui_state_t *s) {
if (!s || !s->runtime_ready) return;
transcribe_params_t tp;
fill_transcribe_params_from_cfg(s, &tp);
transcribe_update_params(&tp);
}
static void update_activity_lights(gui_state_t *s) {
if (!s->record_light || !s->transcribe_light) return;
if (!s || !s->gpu_light || !s->record_light || !s->transcribe_light) return;
const char *gpu_markup = s->use_gpu
? "<span foreground='#1976d2'><b>● GPU</b></span>"
: "<span foreground='#777777'>● CPU</span>";
const int rec_on = (s->recording || s->always_on_running) ? 1 : 0;
const char *rec_markup = rec_on
@@ -83,6 +184,7 @@ static void update_activity_lights(gui_state_t *s) {
? "<span foreground='#ef6c00'><b>● Transcribing</b></span>"
: "<span foreground='#777777'>● Transcribing</span>";
gtk_label_set_markup(GTK_LABEL(s->gpu_light), gpu_markup);
gtk_label_set_markup(GTK_LABEL(s->record_light), rec_markup);
gtk_label_set_markup(GTK_LABEL(s->transcribe_light), tr_markup);
}
@@ -126,6 +228,12 @@ static void update_always_on_button(gui_state_t *s) {
gtk_button_set_label(GTK_BUTTON(s->always_on_btn), s->always_on_running ? "Stop Always-On" : "Start Always-On");
}
static void on_autotype_toggled(GtkToggleButton *btn, gpointer user_data) {
gui_state_t *s = (gui_state_t *)user_data;
s->cfg.autotype_enabled = gtk_toggle_button_get_active(btn) ? 1 : 0;
save_config_best_effort(s);
}
static void log_audio_stats(const float *samples, size_t count, int sample_rate, const char *device_name, char *out, size_t out_sz) {
if (!samples || count == 0 || sample_rate <= 0) {
snprintf(out, out_sz, "Empty capture");
@@ -291,6 +399,7 @@ static void on_vad_value_changed(GtkRange *range, gpointer user_data) {
(void)range;
gui_state_t *s = (gui_state_t *)user_data;
apply_vad_params(s);
save_config_best_effort(s);
}
static gboolean on_level_draw(GtkWidget *widget, cairo_t *cr, gpointer user_data) {
@@ -428,6 +537,8 @@ static void on_always_on_clicked(GtkButton *btn, gpointer user_data) {
update_always_on_button(s);
update_record_button(s);
set_status(s, "Always-on stopped");
s->cfg.always_on = 0;
save_config_best_effort(s);
return;
}
@@ -437,9 +548,12 @@ static void on_always_on_clicked(GtkButton *btn, gpointer user_data) {
update_always_on_button(s);
update_record_button(s);
set_status(s, "Always-on active (continuous monitor)");
s->cfg.always_on = 1;
save_config_best_effort(s);
}
static GtkWidget *add_scale_row(GtkWidget *grid,
int col,
int row,
const char *label_text,
double min,
@@ -451,14 +565,14 @@ static GtkWidget *add_scale_row(GtkWidget *grid,
GtkWidget **out_scale) {
GtkWidget *label = gtk_label_new(label_text);
gtk_label_set_xalign(GTK_LABEL(label), 0.0f);
gtk_grid_attach(GTK_GRID(grid), label, 0, row, 1, 1);
gtk_grid_attach(GTK_GRID(grid), label, col, row, 1, 1);
GtkWidget *scale = gtk_scale_new_with_range(GTK_ORIENTATION_HORIZONTAL, min, max, step);
gtk_scale_set_digits(GTK_SCALE(scale), digits);
gtk_scale_set_draw_value(GTK_SCALE(scale), TRUE);
gtk_range_set_value(GTK_RANGE(scale), value);
gtk_widget_set_hexpand(scale, TRUE);
gtk_grid_attach(GTK_GRID(grid), scale, 1, row, 1, 1);
gtk_grid_attach(GTK_GRID(grid), scale, col + 1, row, 1, 1);
g_signal_connect(scale, "value-changed", G_CALLBACK(on_vad_value_changed), s);
*out_scale = scale;
@@ -471,6 +585,227 @@ static void on_filter_tags_toggled(GtkToggleButton *btn, gpointer user_data) {
int enabled = gtk_toggle_button_get_active(btn) ? 1 : 0;
s->cfg.filter_bracketed_tags = enabled;
transcribe_set_filter_bracketed_tags(enabled);
push_transcribe_runtime_params(s);
save_config_best_effort(s);
}
static int read_spin_int(GtkWidget *spin) {
return gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(spin));
}
static float read_spin_float(GtkWidget *spin) {
return (float)gtk_spin_button_get_value(GTK_SPIN_BUTTON(spin));
}
static void apply_whisper_settings_from_widgets(gui_state_t *s) {
if (!s || !s->sampling_combo) return;
s->cfg.whisper_sampling_strategy = gtk_combo_box_get_active(GTK_COMBO_BOX(s->sampling_combo)) == 1 ? 1 : 0;
s->cfg.whisper_translate = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(s->translate_check)) ? 1 : 0;
s->cfg.whisper_detect_language = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(s->detect_language_check)) ? 1 : 0;
s->cfg.whisper_no_context = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(s->no_context_check)) ? 1 : 0;
s->cfg.whisper_no_timestamps = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(s->no_timestamps_check)) ? 1 : 0;
s->cfg.whisper_single_segment = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(s->single_segment_check)) ? 1 : 0;
s->cfg.whisper_token_timestamps = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(s->token_timestamps_check)) ? 1 : 0;
s->cfg.whisper_split_on_word = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(s->split_on_word_check)) ? 1 : 0;
s->cfg.whisper_suppress_blank = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(s->suppress_blank_check)) ? 1 : 0;
s->cfg.whisper_suppress_nst = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(s->suppress_nst_check)) ? 1 : 0;
s->cfg.whisper_tdrz_enable = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(s->tdrz_enable_check)) ? 1 : 0;
s->cfg.whisper_n_max_text_ctx = read_spin_int(s->n_max_text_ctx_spin);
s->cfg.whisper_offset_ms = read_spin_int(s->offset_ms_spin);
s->cfg.whisper_duration_ms = read_spin_int(s->duration_ms_spin);
s->cfg.whisper_max_len = read_spin_int(s->max_len_spin);
s->cfg.whisper_max_tokens = read_spin_int(s->max_tokens_spin);
s->cfg.whisper_audio_ctx = read_spin_int(s->audio_ctx_spin);
s->cfg.whisper_greedy_best_of = read_spin_int(s->greedy_best_of_spin);
s->cfg.whisper_beam_size = read_spin_int(s->beam_size_spin);
s->cfg.whisper_thold_pt = read_spin_float(s->thold_pt_spin);
s->cfg.whisper_thold_ptsum = read_spin_float(s->thold_ptsum_spin);
s->cfg.whisper_temperature = read_spin_float(s->temperature_spin);
s->cfg.whisper_max_initial_ts = read_spin_float(s->max_initial_ts_spin);
s->cfg.whisper_length_penalty = read_spin_float(s->length_penalty_spin);
s->cfg.whisper_temperature_inc = read_spin_float(s->temperature_inc_spin);
s->cfg.whisper_entropy_thold = read_spin_float(s->entropy_thold_spin);
s->cfg.whisper_logprob_thold = read_spin_float(s->logprob_thold_spin);
s->cfg.whisper_no_speech_thold = read_spin_float(s->no_speech_thold_spin);
s->cfg.whisper_beam_patience = read_spin_float(s->beam_patience_spin);
push_transcribe_runtime_params(s);
save_config_best_effort(s);
}
static void on_whisper_toggle_changed(GtkToggleButton *btn, gpointer user_data) {
(void)btn;
gui_state_t *s = (gui_state_t *)user_data;
apply_whisper_settings_from_widgets(s);
}
static void on_whisper_spin_changed(GtkSpinButton *btn, gpointer user_data) {
(void)btn;
gui_state_t *s = (gui_state_t *)user_data;
apply_whisper_settings_from_widgets(s);
}
static void on_whisper_combo_changed(GtkComboBox *box, gpointer user_data) {
(void)box;
gui_state_t *s = (gui_state_t *)user_data;
apply_whisper_settings_from_widgets(s);
}
static GtkWidget *add_spin_row(GtkWidget *grid,
int col,
int row,
const char *label_text,
double min,
double max,
double step,
double value,
int digits,
gui_state_t *s,
GtkWidget **out_spin) {
GtkWidget *label = gtk_label_new(label_text);
gtk_label_set_xalign(GTK_LABEL(label), 0.0f);
gtk_grid_attach(GTK_GRID(grid), label, col, row, 1, 1);
GtkWidget *spin = gtk_spin_button_new_with_range(min, max, step);
gtk_spin_button_set_digits(GTK_SPIN_BUTTON(spin), digits);
gtk_spin_button_set_value(GTK_SPIN_BUTTON(spin), value);
gtk_widget_set_hexpand(spin, TRUE);
gtk_grid_attach(GTK_GRID(grid), spin, col + 1, row, 1, 1);
g_signal_connect(spin, "value-changed", G_CALLBACK(on_whisper_spin_changed), s);
*out_spin = spin;
return spin;
}
static GtkWidget *build_whisper_settings_tab(gui_state_t *s) {
GtkWidget *scroll = gtk_scrolled_window_new(NULL, NULL);
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
GtkWidget *box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 8);
gtk_container_set_border_width(GTK_CONTAINER(box), 10);
gtk_container_add(GTK_CONTAINER(scroll), box);
GtkWidget *hint = gtk_label_new(
"Whisper decode settings apply globally across model sizes.\n"
"Changes are saved immediately and applied to new transcriptions.");
gtk_label_set_xalign(GTK_LABEL(hint), 0.0f);
gtk_box_pack_start(GTK_BOX(box), hint, FALSE, FALSE, 0);
GtkWidget *checks = gtk_grid_new();
gtk_grid_set_row_spacing(GTK_GRID(checks), 6);
gtk_grid_set_column_spacing(GTK_GRID(checks), 12);
gtk_box_pack_start(GTK_BOX(box), checks, FALSE, FALSE, 0);
GtkWidget *sampling_label = gtk_label_new("Sampling strategy:");
gtk_label_set_xalign(GTK_LABEL(sampling_label), 0.0f);
gtk_grid_attach(GTK_GRID(checks), sampling_label, 0, 0, 1, 1);
s->sampling_combo = gtk_combo_box_text_new();
gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(s->sampling_combo), "Greedy");
gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(s->sampling_combo), "Beam Search");
gtk_combo_box_set_active(GTK_COMBO_BOX(s->sampling_combo), s->cfg.whisper_sampling_strategy ? 1 : 0);
g_signal_connect(s->sampling_combo, "changed", G_CALLBACK(on_whisper_combo_changed), s);
gtk_grid_attach(GTK_GRID(checks), s->sampling_combo, 1, 0, 1, 1);
s->translate_check = gtk_check_button_new_with_label("Translate to English");
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(s->translate_check), s->cfg.whisper_translate ? TRUE : FALSE);
g_signal_connect(s->translate_check, "toggled", G_CALLBACK(on_whisper_toggle_changed), s);
gtk_grid_attach(GTK_GRID(checks), s->translate_check, 0, 1, 1, 1);
s->detect_language_check = gtk_check_button_new_with_label("Auto detect language");
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(s->detect_language_check), s->cfg.whisper_detect_language ? TRUE : FALSE);
g_signal_connect(s->detect_language_check, "toggled", G_CALLBACK(on_whisper_toggle_changed), s);
gtk_grid_attach(GTK_GRID(checks), s->detect_language_check, 1, 1, 1, 1);
s->no_context_check = gtk_check_button_new_with_label("No context");
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(s->no_context_check), s->cfg.whisper_no_context ? TRUE : FALSE);
g_signal_connect(s->no_context_check, "toggled", G_CALLBACK(on_whisper_toggle_changed), s);
gtk_grid_attach(GTK_GRID(checks), s->no_context_check, 0, 2, 1, 1);
s->no_timestamps_check = gtk_check_button_new_with_label("No timestamps");
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(s->no_timestamps_check), s->cfg.whisper_no_timestamps ? TRUE : FALSE);
g_signal_connect(s->no_timestamps_check, "toggled", G_CALLBACK(on_whisper_toggle_changed), s);
gtk_grid_attach(GTK_GRID(checks), s->no_timestamps_check, 1, 2, 1, 1);
s->single_segment_check = gtk_check_button_new_with_label("Single segment");
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(s->single_segment_check), s->cfg.whisper_single_segment ? TRUE : FALSE);
g_signal_connect(s->single_segment_check, "toggled", G_CALLBACK(on_whisper_toggle_changed), s);
gtk_grid_attach(GTK_GRID(checks), s->single_segment_check, 0, 3, 1, 1);
s->token_timestamps_check = gtk_check_button_new_with_label("Token timestamps");
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(s->token_timestamps_check), s->cfg.whisper_token_timestamps ? TRUE : FALSE);
g_signal_connect(s->token_timestamps_check, "toggled", G_CALLBACK(on_whisper_toggle_changed), s);
gtk_grid_attach(GTK_GRID(checks), s->token_timestamps_check, 1, 3, 1, 1);
s->split_on_word_check = gtk_check_button_new_with_label("Split on word");
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(s->split_on_word_check), s->cfg.whisper_split_on_word ? TRUE : FALSE);
g_signal_connect(s->split_on_word_check, "toggled", G_CALLBACK(on_whisper_toggle_changed), s);
gtk_grid_attach(GTK_GRID(checks), s->split_on_word_check, 0, 4, 1, 1);
s->suppress_blank_check = gtk_check_button_new_with_label("Suppress blank tokens");
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(s->suppress_blank_check), s->cfg.whisper_suppress_blank ? TRUE : FALSE);
g_signal_connect(s->suppress_blank_check, "toggled", G_CALLBACK(on_whisper_toggle_changed), s);
gtk_grid_attach(GTK_GRID(checks), s->suppress_blank_check, 1, 4, 1, 1);
s->suppress_nst_check = gtk_check_button_new_with_label("Suppress non-speech tokens");
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(s->suppress_nst_check), s->cfg.whisper_suppress_nst ? TRUE : FALSE);
g_signal_connect(s->suppress_nst_check, "toggled", G_CALLBACK(on_whisper_toggle_changed), s);
gtk_grid_attach(GTK_GRID(checks), s->suppress_nst_check, 0, 5, 1, 1);
s->tdrz_enable_check = gtk_check_button_new_with_label("Enable tinydiarize (TDRZ)");
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(s->tdrz_enable_check), s->cfg.whisper_tdrz_enable ? TRUE : FALSE);
g_signal_connect(s->tdrz_enable_check, "toggled", G_CALLBACK(on_whisper_toggle_changed), s);
gtk_grid_attach(GTK_GRID(checks), s->tdrz_enable_check, 1, 5, 1, 1);
GtkWidget *grid = gtk_grid_new();
gtk_grid_set_row_spacing(GTK_GRID(grid), 6);
gtk_grid_set_column_spacing(GTK_GRID(grid), 10);
gtk_box_pack_start(GTK_BOX(box), grid, FALSE, FALSE, 0);
add_spin_row(grid, 0, 0, "n_max_text_ctx", 0.0, 32768.0, 1.0,
(double)s->cfg.whisper_n_max_text_ctx, 0, s, &s->n_max_text_ctx_spin);
add_spin_row(grid, 2, 0, "offset_ms", 0.0, 60000.0, 10.0,
(double)s->cfg.whisper_offset_ms, 0, s, &s->offset_ms_spin);
add_spin_row(grid, 0, 1, "duration_ms (0=all)", 0.0, 600000.0, 100.0,
(double)s->cfg.whisper_duration_ms, 0, s, &s->duration_ms_spin);
add_spin_row(grid, 2, 1, "max_len", 0.0, 1024.0, 1.0,
(double)s->cfg.whisper_max_len, 0, s, &s->max_len_spin);
add_spin_row(grid, 0, 2, "max_tokens", 0.0, 1024.0, 1.0,
(double)s->cfg.whisper_max_tokens, 0, s, &s->max_tokens_spin);
add_spin_row(grid, 2, 2, "audio_ctx", 0.0, 4096.0, 1.0,
(double)s->cfg.whisper_audio_ctx, 0, s, &s->audio_ctx_spin);
add_spin_row(grid, 0, 3, "thold_pt", 0.0, 1.0, 0.001,
(double)s->cfg.whisper_thold_pt, 3, s, &s->thold_pt_spin);
add_spin_row(grid, 2, 3, "thold_ptsum", 0.0, 1.0, 0.001,
(double)s->cfg.whisper_thold_ptsum, 3, s, &s->thold_ptsum_spin);
add_spin_row(grid, 0, 4, "temperature", 0.0, 2.0, 0.01,
(double)s->cfg.whisper_temperature, 2, s, &s->temperature_spin);
add_spin_row(grid, 2, 4, "max_initial_ts", 0.0, 5.0, 0.01,
(double)s->cfg.whisper_max_initial_ts, 2, s, &s->max_initial_ts_spin);
add_spin_row(grid, 0, 5, "length_penalty", -2.0, 2.0, 0.01,
(double)s->cfg.whisper_length_penalty, 2, s, &s->length_penalty_spin);
add_spin_row(grid, 2, 5, "temperature_inc", 0.0, 1.0, 0.01,
(double)s->cfg.whisper_temperature_inc, 2, s, &s->temperature_inc_spin);
add_spin_row(grid, 0, 6, "entropy_thold", -5.0, 10.0, 0.01,
(double)s->cfg.whisper_entropy_thold, 2, s, &s->entropy_thold_spin);
add_spin_row(grid, 2, 6, "logprob_thold", -5.0, 1.0, 0.01,
(double)s->cfg.whisper_logprob_thold, 2, s, &s->logprob_thold_spin);
add_spin_row(grid, 0, 7, "no_speech_thold", 0.0, 1.0, 0.01,
(double)s->cfg.whisper_no_speech_thold, 2, s, &s->no_speech_thold_spin);
add_spin_row(grid, 2, 7, "greedy_best_of", 1.0, 10.0, 1.0,
(double)s->cfg.whisper_greedy_best_of, 0, s, &s->greedy_best_of_spin);
add_spin_row(grid, 0, 8, "beam_size", 1.0, 20.0, 1.0,
(double)s->cfg.whisper_beam_size, 0, s, &s->beam_size_spin);
add_spin_row(grid, 2, 8, "beam_patience", -1.0, 5.0, 0.01,
(double)s->cfg.whisper_beam_patience, 2, s, &s->beam_patience_spin);
return scroll;
}
static int cmp_str_ptr(const void *a, const void *b) {
@@ -517,6 +852,89 @@ static const char *base_name(const char *path) {
return slash ? (slash + 1) : path;
}
static long long model_size_bytes(const char *path) {
struct stat st;
if (!path) return -1;
if (stat(path, &st) != 0) return -1;
return (long long)st.st_size;
}
static void format_size_human(long long bytes, char *out, size_t out_sz) {
static const char *units[] = { "B", "KB", "MB", "GB", "TB" };
double v = (double)(bytes < 0 ? 0 : bytes);
int unit = 0;
while (v >= 1024.0 && unit < 4) {
v /= 1024.0;
unit++;
}
snprintf(out, out_sz, "%.1f %s", v, units[unit]);
}
typedef struct {
transcribe_params_t tp;
int rc;
volatile gint done;
} transcribe_init_worker_t;
static gpointer transcribe_init_worker(gpointer user_data) {
transcribe_init_worker_t *w = (transcribe_init_worker_t *)user_data;
w->rc = transcribe_init(&w->tp);
g_atomic_int_set(&w->done, 1);
return NULL;
}
static int run_model_init_with_progress(gui_state_t *s) {
GtkWidget *dialog = gtk_dialog_new();
gtk_window_set_title(GTK_WINDOW(dialog), "Loading Model");
gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
gtk_window_set_resizable(GTK_WINDOW(dialog), FALSE);
GtkWidget *content = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
GtkWidget *box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 8);
gtk_container_set_border_width(GTK_CONTAINER(box), 12);
gtk_box_pack_start(GTK_BOX(content), box, TRUE, TRUE, 0);
char msg[640];
snprintf(msg, sizeof(msg), "Loading %s (%s mode)...",
base_name(s->cfg.model_path), s->use_gpu ? "GPU" : "CPU");
GtkWidget *label = gtk_label_new(msg);
gtk_label_set_xalign(GTK_LABEL(label), 0.0f);
gtk_box_pack_start(GTK_BOX(box), label, FALSE, FALSE, 0);
GtkWidget *bar = gtk_progress_bar_new();
gtk_progress_bar_set_show_text(GTK_PROGRESS_BAR(bar), TRUE);
gtk_progress_bar_set_text(GTK_PROGRESS_BAR(bar), "Initializing whisper context...");
gtk_box_pack_start(GTK_BOX(box), bar, FALSE, FALSE, 0);
gtk_widget_show_all(dialog);
transcribe_init_worker_t worker;
memset(&worker, 0, sizeof(worker));
fill_transcribe_params_from_cfg(s, &worker.tp);
worker.done = 0;
GThread *thread = g_thread_new("transcribe-init", transcribe_init_worker, &worker);
int ticks = 0;
while (!g_atomic_int_get(&worker.done)) {
while (gtk_events_pending()) gtk_main_iteration();
gtk_progress_bar_pulse(GTK_PROGRESS_BAR(bar));
ticks++;
if ((ticks % 20) == 0) {
char txt[128];
snprintf(txt, sizeof(txt), "Loading... %ds", ticks / 20);
gtk_progress_bar_set_text(GTK_PROGRESS_BAR(bar), txt);
}
g_usleep(50000);
}
g_thread_join(thread);
gtk_widget_destroy(dialog);
return worker.rc;
}
static int run_startup_dialog(gui_state_t *s, GtkWindow *parent) {
char model_paths[64][512];
int model_count = scan_model_paths(model_paths, 64);
@@ -570,7 +988,17 @@ static int run_startup_dialog(gui_state_t *s, GtkWindow *parent) {
int active_model_index = 0;
for (int i = 0; i < model_count; ++i) {
const char *path = model_paths[i];
gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(model_combo), path, base_name(path));
char entry[256];
long long sz = model_size_bytes(path);
if (sz >= 0) {
char pretty[32];
format_size_human(sz, pretty, sizeof(pretty));
snprintf(entry, sizeof(entry), "%.220s (%s)", base_name(path), pretty);
} else {
snprintf(entry, sizeof(entry), "%.250s", base_name(path));
}
gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(model_combo), path, entry);
if (strcmp(path, s->cfg.model_path) == 0) {
active_model_index = i;
}
@@ -589,6 +1017,7 @@ static int run_startup_dialog(gui_state_t *s, GtkWindow *parent) {
snprintf(s->cfg.model_path, sizeof(s->cfg.model_path), "%s", active_model);
}
s->cfg.use_gpu = s->use_gpu;
save_config_best_effort(s);
gtk_widget_destroy(dialog);
return 0;
}
@@ -606,14 +1035,7 @@ static void on_app_activate(GtkApplication *app, gpointer user_data) {
return;
}
transcribe_params_t tp = {0};
snprintf(tp.model_path, sizeof(tp.model_path), "%s", s->cfg.model_path);
snprintf(tp.language, sizeof(tp.language), "%s", s->cfg.language);
tp.use_gpu = s->use_gpu;
tp.n_threads = (int)sysconf(_SC_NPROCESSORS_ONLN);
tp.filter_bracketed_tags = s->cfg.filter_bracketed_tags;
if (transcribe_init(&tp) != 0) {
if (run_model_init_with_progress(s) != 0) {
GtkWidget *msg = gtk_message_dialog_new(
NULL,
GTK_DIALOG_MODAL,
@@ -644,32 +1066,36 @@ static void on_app_activate(GtkApplication *app, gpointer user_data) {
}
s->window = gtk_application_window_new(app);
gtk_window_set_title(GTK_WINDOW(s->window), "voice_linux");
gtk_window_set_default_size(GTK_WINDOW(s->window), 760, 640);
char title[128];
snprintf(title, sizeof(title), "voice_linux %s", VOICE_LINUX_VERSION);
gtk_window_set_title(GTK_WINDOW(s->window), title);
gtk_window_set_default_size(GTK_WINDOW(s->window), 760, 780);
GtkWidget *outer = gtk_box_new(GTK_ORIENTATION_VERTICAL, 8);
gtk_container_set_border_width(GTK_CONTAINER(outer), 12);
gtk_container_add(GTK_CONTAINER(s->window), outer);
char info[1024];
snprintf(info, sizeof(info), "Version: %s\nDevice: %s\nModel: %s\nLanguage: %s\nGPU: %s",
VOICE_LINUX_VERSION, s->cfg.audio_device, s->cfg.model_path, s->cfg.language, s->use_gpu ? "on" : "off");
GtkWidget *info_label = gtk_label_new(info);
gtk_label_set_xalign(GTK_LABEL(info_label), 0.0f);
gtk_box_pack_start(GTK_BOX(outer), info_label, FALSE, FALSE, 0);
s->status_label = gtk_label_new("Idle");
gtk_label_set_xalign(GTK_LABEL(s->status_label), 0.0f);
gtk_box_pack_start(GTK_BOX(outer), s->status_label, FALSE, FALSE, 0);
GtkWidget *notebook = gtk_notebook_new();
gtk_box_pack_start(GTK_BOX(outer), notebook, TRUE, TRUE, 0);
GtkWidget *main_page = gtk_box_new(GTK_ORIENTATION_VERTICAL, 8);
gtk_container_set_border_width(GTK_CONTAINER(main_page), 6);
GtkWidget *lights_row = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 14);
s->gpu_light = gtk_label_new(NULL);
s->record_light = gtk_label_new(NULL);
s->transcribe_light = gtk_label_new(NULL);
gtk_label_set_use_markup(GTK_LABEL(s->gpu_light), TRUE);
gtk_label_set_use_markup(GTK_LABEL(s->record_light), TRUE);
gtk_label_set_use_markup(GTK_LABEL(s->transcribe_light), TRUE);
gtk_box_pack_start(GTK_BOX(lights_row), s->gpu_light, FALSE, FALSE, 0);
gtk_box_pack_start(GTK_BOX(lights_row), s->record_light, FALSE, FALSE, 0);
gtk_box_pack_start(GTK_BOX(lights_row), s->transcribe_light, FALSE, FALSE, 0);
gtk_box_pack_start(GTK_BOX(outer), lights_row, FALSE, FALSE, 0);
gtk_box_pack_start(GTK_BOX(main_page), lights_row, FALSE, FALSE, 0);
s->record_btn = gtk_button_new_with_label("Start Recording");
gtk_widget_set_name(s->record_btn, "record-btn");
@@ -694,24 +1120,31 @@ static void on_app_activate(GtkApplication *app, gpointer user_data) {
update_record_button(s);
g_signal_connect(s->record_btn, "clicked", G_CALLBACK(on_record_clicked), s);
gtk_box_pack_start(GTK_BOX(outer), s->record_btn, FALSE, FALSE, 0);
update_activity_lights(s);
s->always_on_btn = gtk_button_new_with_label("Start Always-On");
g_signal_connect(s->always_on_btn, "clicked", G_CALLBACK(on_always_on_clicked), s);
gtk_box_pack_start(GTK_BOX(outer), s->always_on_btn, FALSE, FALSE, 0);
GtkWidget *btn_row = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 8);
gtk_box_pack_start(GTK_BOX(btn_row), s->record_btn, TRUE, TRUE, 0);
gtk_box_pack_start(GTK_BOX(btn_row), s->always_on_btn, TRUE, TRUE, 0);
gtk_box_pack_start(GTK_BOX(main_page), btn_row, FALSE, FALSE, 0);
update_activity_lights(s);
GtkWidget *checks_row = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 12);
s->type_check = gtk_check_button_new_with_label("Type transcript into focused window");
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(s->type_check), s->cfg.autotype_enabled ? TRUE : FALSE);
gtk_box_pack_start(GTK_BOX(outer), s->type_check, FALSE, FALSE, 0);
g_signal_connect(s->type_check, "toggled", G_CALLBACK(on_autotype_toggled), s);
gtk_box_pack_start(GTK_BOX(checks_row), s->type_check, TRUE, TRUE, 0);
s->filter_tags_check = gtk_check_button_new_with_label("Filter bracketed tags like [laughs]");
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(s->filter_tags_check), s->cfg.filter_bracketed_tags ? TRUE : FALSE);
g_signal_connect(s->filter_tags_check, "toggled", G_CALLBACK(on_filter_tags_toggled), s);
gtk_box_pack_start(GTK_BOX(outer), s->filter_tags_check, FALSE, FALSE, 0);
gtk_box_pack_start(GTK_BOX(checks_row), s->filter_tags_check, TRUE, TRUE, 0);
gtk_box_pack_start(GTK_BOX(main_page), checks_row, FALSE, FALSE, 0);
GtkWidget *meter_frame = gtk_frame_new("Live Microphone Level");
gtk_box_pack_start(GTK_BOX(outer), meter_frame, FALSE, FALSE, 0);
gtk_box_pack_start(GTK_BOX(main_page), meter_frame, FALSE, FALSE, 0);
GtkWidget *meter_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 6);
gtk_container_set_border_width(GTK_CONTAINER(meter_box), 8);
@@ -727,17 +1160,17 @@ static void on_app_activate(GtkApplication *app, gpointer user_data) {
gtk_grid_set_column_spacing(GTK_GRID(grid), 10);
gtk_box_pack_start(GTK_BOX(meter_box), grid, FALSE, FALSE, 0);
add_scale_row(grid, 0, "Threshold (0.0-1.0)", 0.0, 1.0, 0.001,
add_scale_row(grid, 0, 0, "Threshold (0.0-1.0)", 0.0, 1.0, 0.001,
s->cfg.vad_peak_threshold, 3, s, &s->threshold_scale);
add_scale_row(grid, 1, "Trigger ms (above threshold)", 10.0, 2000.0, 10.0,
add_scale_row(grid, 2, 0, "Trigger ms (above threshold)", 10.0, 2000.0, 10.0,
(double)s->cfg.vad_trigger_ms, 0, s, &s->trigger_scale);
add_scale_row(grid, 2, "Release ms (below threshold)", 10.0, 4000.0, 10.0,
add_scale_row(grid, 0, 1, "Release ms (below threshold)", 10.0, 4000.0, 10.0,
(double)s->cfg.vad_release_ms, 0, s, &s->release_scale);
add_scale_row(grid, 3, "Preroll ms (audio before trigger)", 0.0, 3000.0, 10.0,
add_scale_row(grid, 2, 1, "Preroll ms (audio before trigger)", 0.0, 3000.0, 10.0,
(double)s->cfg.vad_preroll_ms, 0, s, &s->preroll_scale);
add_scale_row(grid, 4, "Min speech ms", 50.0, 5000.0, 10.0,
add_scale_row(grid, 0, 2, "Min speech ms", 50.0, 5000.0, 10.0,
(double)s->cfg.vad_min_speech_ms, 0, s, &s->min_speech_scale);
add_scale_row(grid, 5, "Max speech ms", 200.0, 60000.0, 100.0,
add_scale_row(grid, 2, 2, "Max speech ms", 200.0, 60000.0, 100.0,
(double)s->cfg.vad_max_speech_ms, 0, s, &s->max_speech_scale);
GtkWidget *hint = gtk_label_new(
@@ -751,9 +1184,10 @@ static void on_app_activate(GtkApplication *app, gpointer user_data) {
gtk_box_pack_start(GTK_BOX(meter_box), s->debug_label, FALSE, FALSE, 0);
GtkWidget *frame = gtk_frame_new("Transcript Log");
gtk_box_pack_start(GTK_BOX(outer), frame, TRUE, TRUE, 0);
gtk_box_pack_start(GTK_BOX(main_page), frame, TRUE, TRUE, 0);
GtkWidget *scroll = gtk_scrolled_window_new(NULL, NULL);
gtk_widget_set_size_request(scroll, -1, 320);
gtk_container_add(GTK_CONTAINER(frame), scroll);
s->transcript_view = gtk_text_view_new();
@@ -762,6 +1196,11 @@ static void on_app_activate(GtkApplication *app, gpointer user_data) {
apply_vad_params(s);
update_always_on_button(s);
gtk_notebook_append_page(GTK_NOTEBOOK(notebook), main_page, gtk_label_new("Main"));
GtkWidget *whisper_page = build_whisper_settings_tab(s);
gtk_notebook_append_page(GTK_NOTEBOOK(notebook), whisper_page, gtk_label_new("Whisper"));
gtk_widget_show_all(s->window);
s->meter_timer_id = g_timeout_add(60, meter_tick_cb, s);
@@ -801,6 +1240,7 @@ int main(int argc, char **argv) {
const char *cfg_path = arg_value(argc, argv, "--config");
if (!cfg_path) cfg_path = "./config.ini";
snprintf(st.cfg_path, sizeof(st.cfg_path), "%s", cfg_path);
if (config_load_file(cfg_path, &st.cfg) != 0) {
fprintf(stderr, "warning: failed to load %s, using defaults\n", cfg_path);
}
+57 -4
View File
@@ -80,6 +80,49 @@ void transcribe_set_filter_bracketed_tags(int enabled) {
static struct whisper_context *g_ctx = NULL;
static transcribe_params_t g_params;
static void apply_full_params_from_config(struct whisper_full_params *dst, const transcribe_params_t *src) {
if (!dst || !src) return;
dst->n_threads = src->n_threads > 0 ? src->n_threads : 4;
dst->n_max_text_ctx = src->n_max_text_ctx;
dst->offset_ms = src->offset_ms;
dst->duration_ms = src->duration_ms;
dst->translate = src->translate ? true : false;
dst->detect_language = src->detect_language ? true : false;
dst->no_context = src->no_context ? true : false;
dst->no_timestamps = src->no_timestamps ? true : false;
dst->single_segment = src->single_segment ? true : false;
dst->token_timestamps = src->token_timestamps ? true : false;
dst->thold_pt = src->thold_pt;
dst->thold_ptsum = src->thold_ptsum;
dst->max_len = src->max_len;
dst->split_on_word = src->split_on_word ? true : false;
dst->max_tokens = src->max_tokens;
dst->audio_ctx = src->audio_ctx;
dst->tdrz_enable = src->tdrz_enable ? true : false;
dst->suppress_blank = src->suppress_blank ? true : false;
dst->suppress_nst = src->suppress_nst ? true : false;
dst->temperature = src->temperature;
dst->max_initial_ts = src->max_initial_ts;
dst->length_penalty = src->length_penalty;
dst->temperature_inc = src->temperature_inc;
dst->entropy_thold = src->entropy_thold;
dst->logprob_thold = src->logprob_thold;
dst->no_speech_thold = src->no_speech_thold;
dst->greedy.best_of = src->greedy_best_of;
dst->beam_search.beam_size = src->beam_size;
dst->beam_search.patience = src->beam_patience;
dst->language = src->language[0] ? src->language : "en";
}
int transcribe_init(const transcribe_params_t *params) {
if (!params) return -1;
g_params = *params;
@@ -95,17 +138,22 @@ int transcribe_init(const transcribe_params_t *params) {
return 0;
}
void transcribe_update_params(const transcribe_params_t *params) {
if (!params) return;
g_params = *params;
transcribe_set_filter_bracketed_tags(g_params.filter_bracketed_tags);
}
char *transcribe_buffer(const float *samples, size_t count) {
if (!g_ctx || !samples || count == 0) return NULL;
struct whisper_full_params params = whisper_full_default_params(WHISPER_SAMPLING_GREEDY);
enum whisper_sampling_strategy strategy = g_params.sampling_strategy ? WHISPER_SAMPLING_BEAM_SEARCH : WHISPER_SAMPLING_GREEDY;
struct whisper_full_params params = whisper_full_default_params(strategy);
params.print_progress = false;
params.print_special = false;
params.print_realtime = false;
params.print_timestamps = false;
params.translate = false;
params.n_threads = g_params.n_threads > 0 ? g_params.n_threads : 4;
params.language = g_params.language[0] ? g_params.language : "en";
apply_full_params_from_config(&params, &g_params);
if (whisper_full(g_ctx, params, samples, (int)count) != 0) {
fprintf(stderr, "transcribe: whisper_full failed\n");
@@ -161,6 +209,11 @@ int transcribe_init(const transcribe_params_t *params) {
return 0;
}
void transcribe_update_params(const transcribe_params_t *params) {
if (!params) return;
g_params = *params;
}
char *transcribe_buffer(const float *samples, size_t count) {
(void)samples;
(void)count;
+38
View File
@@ -9,9 +9,47 @@ typedef struct {
int use_gpu;
int n_threads;
int filter_bracketed_tags;
int sampling_strategy; // 0=greedy, 1=beam search
int n_max_text_ctx;
int offset_ms;
int duration_ms;
int translate;
int detect_language;
int no_context;
int no_timestamps;
int single_segment;
int token_timestamps;
float thold_pt;
float thold_ptsum;
int max_len;
int split_on_word;
int max_tokens;
int audio_ctx;
int tdrz_enable;
int suppress_blank;
int suppress_nst;
float temperature;
float max_initial_ts;
float length_penalty;
float temperature_inc;
float entropy_thold;
float logprob_thold;
float no_speech_thold;
int greedy_best_of;
int beam_size;
float beam_patience;
} transcribe_params_t;
int transcribe_init(const transcribe_params_t *params);
void transcribe_update_params(const transcribe_params_t *params);
void transcribe_set_filter_bracketed_tags(int enabled);
char *transcribe_buffer(const float *samples, size_t count);
void transcribe_cleanup(void);