The officially official Devuan Forum!

You are not logged in.

#26 2025-07-05 23:53:15

greenjeans
Member
Registered: 2017-04-07
Posts: 1,019  
Website

Re: ALSA-only purists: Question, new GUI app for the mixer and EQ?

Tested earlier in a live session with Vuu-do Mate mini that's also alsa-only, worked great! The mini doesn't have a music app, so I installed Strawberry from the repo for testing (had previously tested with Exaile). Really cool, the pre-set functions are working perfectly. Few more little things to do and it will be ready for packaging.

EDIT: Forgot to mention I also tried it with some different themes in that session, looks fine! Looked really cool in fact in the BlackMate theme.

EDIT2: Just added a window icon to the script, looks good and adds a tiny bit of color to it. Decided on a name for the app, going to call it "AlsaTune" which seems good, pretty much describes what the app does in just a few letters! Now just need to do some more testing, and then i'm ready to package which should be a breeze, though I do need to add a fair amount of depends to the control, and also a good size readme.txt with more info and sample .asoundrc and .asound.conf files to help folks with the process. I'm excited going forward, in the end i'd like to help create more add-ons that extend the functionality of alsa, and remove completely the need for things like Pulse and Pipewire, and make configuration a breeze with simple gui apps like this.

kb4vrq.png

Last edited by greenjeans (2025-07-06 22:55:35)


https://sourceforge.net/projects/vuu-do/ New Vuu-do isos uploaded April 2025!
Vuu-do GNU/Linux, minimal Devuan-based Openbox and Mate systems to build on. Also a max version for OB.
Devuan 5 mate-mini iso, pure Devuan, 100% no-vuu-do. wink Devuan 6 version also available for testing.
Please donate to support Devuan and init freedom! https://devuan.org/os/donate

Offline

#27 2025-07-09 16:14:23

greenjeans
Member
Registered: 2017-04-07
Posts: 1,019  
Website

Re: ALSA-only purists: Question, new GUI app for the mixer and EQ?

Yep, going forward i'll make a git account, just taking baby steps
right now as much as I can, this is a lot of stuff to learn. Here's what's
going on currently for anyone who might like to test:

So this app assumes that users will be alsa-purists who already have a working
alsa-only system with all the goodies including packages alsamixergui, libasound2-plugin-equal,
alsa-utils etc. And already have working terminal versions of the mixer and EQ and a
properly configured ~/.asoundrc and /etc/.asound.conf.

You must not have any sound server daemons or controls active whatsoever, no Pulse
or Pipe etc. Best not to have them installed at all. VLC drags in libpulse0, but that's
just a library and doesn't affect function.

To compile the source, you will need : GCC, libgtk-3-dev, and libasound2-dev and all their
dependencies.

So copy the code here into your text-editor, save it as "mxeq.c" in any folder you
like. The open a terminal in that same folder to compile (you don't need to be root):

gcc -o mxeq mxeq.c $(pkg-config --cflags --libs gtk+-3.0 alsa glib-2.0) -Wall

Then make the resulting binary executable, then run it, if everything is configged
properly it will be there in all it's glory! If you have Pulse running you will
get a gui with no content but a volume slider maybe, won't hurt anything, just won't
work right. Here's the source:

#include <gtk/gtk.h>
#include <alsa/asoundlib.h>
#include <stdio.h>
#include <string.h>
#include <glib/gstdio.h>

typedef struct {
    snd_mixer_t *mixer;
    snd_mixer_elem_t *elem;
    GtkWidget *scale;
    const char *channel_name;
} MixerChannel;

typedef struct {
    snd_mixer_t *mixer;
    MixerChannel *channels;
    int num_channels;
} MixerData;

typedef struct {
    snd_ctl_t *ctl;
    snd_ctl_elem_value_t *val;
    GtkWidget *scale;
    const char *band_name;
} EQBand;

typedef struct {
    snd_ctl_t *ctl;
    EQBand *bands;
    int num_bands;
} EQData;

// Callback to save preset to ~/.local/share/mxeq/presets.csv
static void save_preset(GtkWidget *button, gpointer user_data) {
    EQData *eq_data = (EQData *)user_data;
    GtkWidget *entry = g_object_get_data(G_OBJECT(button), "preset_entry");
    GtkWidget *combo = g_object_get_data(G_OBJECT(button), "preset_combo");
    GtkWidget *window = g_object_get_data(G_OBJECT(button), "window");
    const char *preset_name = gtk_entry_get_text(GTK_ENTRY(entry));

    if (!preset_name || strlen(preset_name) == 0) {
        GtkWidget *dialog = gtk_message_dialog_new(GTK_WINDOW(window), GTK_DIALOG_MODAL,
                                                  GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
                                                  "Please enter a preset name.");
        gtk_dialog_run(GTK_DIALOG(dialog));
        gtk_widget_destroy(dialog);
        return;
    }

    // Check for duplicate preset name
    const char *home = g_get_home_dir();
    char *preset_path = g_build_filename(home, ".local", "share", "mxeq", "presets.csv", NULL);
    FILE *fp_check = fopen(preset_path, "r");
    if (fp_check) {
        char line[256];
        while (fgets(line, sizeof(line), fp_check)) {
            char *name = strtok(line, ":");
            if (name && strcmp(name, preset_name) == 0) {
                GtkWidget *dialog = gtk_message_dialog_new(GTK_WINDOW(window), GTK_DIALOG_MODAL,
                                                          GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
                                                          "Preset name already exists. Choose a different name.");
                gtk_dialog_run(GTK_DIALOG(dialog));
                gtk_widget_destroy(dialog);
                fclose(fp_check);
                g_free(preset_path);
                return;
            }
        }
        fclose(fp_check);
    }

    // Create directory if it doesn't exist
    char *dir_path = g_build_filename(home, ".local", "share", "mxeq", NULL);
    g_mkdir_with_parents(dir_path, 0755);
    g_free(dir_path);

    // Open file in append mode
    FILE *fp = fopen(preset_path, "a");
    if (!fp) {
        GtkWidget *dialog = gtk_message_dialog_new(GTK_WINDOW(window), GTK_DIALOG_MODAL,
                                                  GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
                                                  "Failed to save preset: Could not open file.");
        gtk_dialog_run(GTK_DIALOG(dialog));
        gtk_widget_destroy(dialog);
        g_free(preset_path);
        return;
    }

    // Collect EQ values
    GString *values = g_string_new("");
    for (int i = 0; i < eq_data->num_bands; i++) {
        gdouble value = gtk_range_get_value(GTK_RANGE(eq_data->bands[i].scale));
        g_string_append_printf(values, "%.2f", value);
        if (i < eq_data->num_bands - 1) {
            g_string_append(values, ",");
        }
    }

    // Write preset to file
    fprintf(fp, "%s:%s\n", preset_name, values->str);
    fclose(fp);
    g_string_free(values, TRUE);

    // Add to combo box and set as active
    gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), preset_name);
    gtk_combo_box_set_active_id(GTK_COMBO_BOX(combo), preset_name);

    // Show confirmation dialog
    GtkWidget *dialog = gtk_message_dialog_new(GTK_WINDOW(window), GTK_DIALOG_MODAL,
                                              GTK_MESSAGE_INFO, GTK_BUTTONS_OK,
                                              "Preset Saved!");
    gtk_dialog_run(GTK_DIALOG(dialog));
    gtk_widget_destroy(dialog);

    // Clear entry
    gtk_entry_set_text(GTK_ENTRY(entry), "");
    g_free(preset_path);
}

// Callback to apply selected preset
static void apply_preset(GtkComboBox *combo, gpointer user_data) {
    EQData *eq_data = (EQData *)user_data;
    char *preset_name = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(combo));
    if (!preset_name || g_strcmp0(preset_name, "Choose EQ Pre-set") == 0) {
        g_free(preset_name);
        return;
    }

    // Build preset file path
    const char *home = g_get_home_dir();
    char *preset_path = g_build_filename(home, ".local", "share", "mxeq", "presets.csv", NULL);

    // Open preset file
    FILE *fp = fopen(preset_path, "r");
    if (!fp) {
        fprintf(stderr, "Failed to open presets.csv\n");
        g_free(preset_name);
        g_free(preset_path);
        return;
    }

    // Read file to find matching preset
    char line[256];
    while (fgets(line, sizeof(line), fp)) {
        char *name = strtok(line, ":");
        if (name && strcmp(name, preset_name) == 0) {
            char *values = strtok(NULL, "\n");
            if (values) {
                char *token = strtok(values, ",");
                int i = 0;
                while (token && i < eq_data->num_bands) {
                    gdouble value = g_ascii_strtod(token, NULL);
                    gtk_range_set_value(GTK_RANGE(eq_data->bands[i].scale), value);
                    long alsa_value = (long)(value * 100.0);
                    snd_ctl_elem_value_set_integer(eq_data->bands[i].val, 0, alsa_value);
                    snd_ctl_elem_value_set_integer(eq_data->bands[i].val, 1, alsa_value);
                    snd_ctl_elem_write(eq_data->bands[i].ctl, eq_data->bands[i].val);
                    token = strtok(NULL, ",");
                    i++;
                }
            }
            break;
        }
    }
    fclose(fp);
    g_free(preset_name);
    g_free(preset_path);
}

// Load presets into combo box at startup
static void load_presets(GtkComboBoxText *combo) {
    gtk_combo_box_text_append_text(combo, "Choose EQ Pre-set");
    const char *home = g_get_home_dir();
    char *preset_path = g_build_filename(home, ".local", "share", "mxeq", "presets.csv", NULL);
    FILE *fp = fopen(preset_path, "r");
    if (fp) {
        char line[256];
        while (fgets(line, sizeof(line), fp)) {
            char *name = strtok(line, ":");
            if (name) {
                gtk_combo_box_text_append_text(combo, name);
            }
        }
        fclose(fp);
    }
    gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0); // Default to "Choose EQ Pre-set"
    g_free(preset_path);
}

static void slider_changed(GtkRange *range, MixerChannel *channel) {
    gdouble value = gtk_range_get_value(range);
    long min, max;
    snd_mixer_selem_get_playback_volume_range(channel->elem, &min, &max);
    long alsa_value = (long)(value * (max - min) + min);
    snd_mixer_selem_set_playback_volume_all(channel->elem, alsa_value);
}

static void eq_slider_changed(GtkRange *range, EQBand *band) {
    gdouble value = gtk_range_get_value(range);
    long alsa_value = (long)(value * 100.0);
    snd_ctl_elem_value_set_integer(band->val, 0, alsa_value); // Left channel
    snd_ctl_elem_value_set_integer(band->val, 1, alsa_value); // Right channel
    snd_ctl_elem_write(band->ctl, band->val);
}

static void init_alsa_mixer(MixerData *data) {
    if (snd_mixer_open(&data->mixer, 0) < 0) {
        fprintf(stderr, "Failed to open ALSA mixer\n");
        return;
    }
    if (snd_mixer_attach(data->mixer, "default") < 0) {
        fprintf(stderr, "Failed to attach mixer to default device\n");
        snd_mixer_close(data->mixer);
        return;
    }
    if (snd_mixer_selem_register(data->mixer, NULL, NULL) < 0) {
        fprintf(stderr, "Failed to register mixer\n");
        snd_mixer_close(data->mixer);
        return;
    }
    if (snd_mixer_load(data->mixer) < 0) {
        fprintf(stderr, "Failed to load mixer\n");
        snd_mixer_close(data->mixer);
        return;
    }

    const char *channel_names[] = {"Master", "Speaker", "PCM", "Headphone", "Mic", "Mic Boost", "Beep"};
    int max_channels = G_N_ELEMENTS(channel_names);
    data->channels = g_new(MixerChannel, max_channels);
    data->num_channels = 0;

    for (int i = 0; i < max_channels; i++) {
        snd_mixer_selem_id_t *sid;
        snd_mixer_selem_id_alloca(&sid);
        snd_mixer_selem_id_set_name(sid, channel_names[i]);
        snd_mixer_elem_t *elem = snd_mixer_find_selem(data->mixer, sid);
        if (elem) {
            data->channels[data->num_channels].elem = elem;
            data->channels[data->num_channels].channel_name = channel_names[i];
            data->num_channels++;
        }
    }
}

static void init_alsa_eq(EQData *data) {
    int err;
    if ((err = snd_ctl_open(&data->ctl, "equal", 0)) < 0) {
        fprintf(stderr, "Failed to open ALSA control 'equal': %s\n", snd_strerror(err));
        return;
    }

    snd_ctl_elem_list_t *list;
    snd_ctl_elem_list_alloca(&list);
    if ((err = snd_ctl_elem_list(data->ctl, list)) < 0) {
        fprintf(stderr, "Failed to get initial control list: %s\n", snd_strerror(err));
        snd_ctl_close(data->ctl);
        return;
    }

    int num_controls = snd_ctl_elem_list_get_count(list);
    if (num_controls <= 0) {
        fprintf(stderr, "No controls found for 'equal' device\n");
        snd_ctl_close(data->ctl);
        return;
    }

    if ((err = snd_ctl_elem_list_alloc_space(list, num_controls)) < 0) {
        fprintf(stderr, "Failed to allocate control list space: %s\n", snd_strerror(err));
        snd_ctl_close(data->ctl);
        return;
    }
    if ((err = snd_ctl_elem_list(data->ctl, list)) < 0) {
        fprintf(stderr, "Failed to list controls: %s\n", snd_strerror(err));
        snd_ctl_close(data->ctl);
        return;
    }

    num_controls = snd_ctl_elem_list_get_used(list);
    data->bands = g_new(EQBand, num_controls);
    data->num_bands = 0;

    for (int i = 0; i < num_controls; i++) {
        snd_ctl_elem_id_t *id;
        snd_ctl_elem_id_alloca(&id);
        snd_ctl_elem_list_get_id(list, i, id);

        const char *name = snd_ctl_elem_id_get_name(id);
        unsigned int numid = snd_ctl_elem_id_get_numid(id);
        snd_ctl_elem_iface_t iface = snd_ctl_elem_id_get_interface(id);

        if (iface != SND_CTL_ELEM_IFACE_MIXER) {
            fprintf(stderr, "Skipping non-mixer control: %s (interface=%d)\n", name, iface);
            continue;
        }

        snd_ctl_elem_value_t *val;
        if ((err = snd_ctl_elem_value_malloc(&val)) < 0) {
            fprintf(stderr, "Failed to allocate value for control '%s': %s\n", name, snd_strerror(err));
            continue;
        }
        snd_ctl_elem_value_set_id(val, id);

        if ((err = snd_ctl_elem_read(data->ctl, val)) < 0) {
            fprintf(stderr, "Failed to read control '%s' (numid %u): %s\n", name, numid, snd_strerror(err));
            snd_ctl_elem_value_free(val);
            continue;
        }

        data->bands[data->num_bands].val = val;
        data->bands[data->num_bands].band_name = g_strdup(name);
        data->bands[data->num_bands].ctl = data->ctl;
        data->num_bands++;
    }

    if (data->num_bands == 0) {
        fprintf(stderr, "No EQ bands found\n");
    }
    snd_ctl_elem_list_free_space(list);
}

static void cleanup_alsa(MixerData *mixer_data, EQData *eq_data) {
    if (mixer_data->mixer) {
        snd_mixer_close(mixer_data->mixer);
        g_free(mixer_data->channels);
    }
    if (eq_data->ctl) {
        for (int i = 0; i < eq_data->num_bands; i++) {
            if (eq_data->bands[i].val) {
                snd_ctl_elem_value_free(eq_data->bands[i].val);
            }
            g_free((char *)eq_data->bands[i].band_name);
        }
        snd_ctl_close(eq_data->ctl);
        g_free(eq_data->bands);
    }
}

int main(int argc, char *argv[]) {
    gtk_init(&argc, &argv);

    // Set the default window icon
    gtk_window_set_default_icon_name("audio-speakers");

    MixerData mixer_data = {0};
    EQData eq_data = {0};
    init_alsa_mixer(&mixer_data);
    init_alsa_eq(&eq_data);

    // Create CSS provider for custom styling
    GtkCssProvider *css_provider = gtk_css_provider_new();
    gtk_css_provider_load_from_data(css_provider,
        "frame {"
        "  border-width: 2px;"
        "  border-style: solid;"
        "  border-color: #333333;"
        "  box-shadow: none;"
        "  border-radius: 4px;"
        "}",
        -1, NULL);
    gtk_style_context_add_provider_for_screen(
        gdk_screen_get_default(),
        GTK_STYLE_PROVIDER(css_provider),
        GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
    g_object_unref(css_provider);

    // Create window
    GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_window_set_title(GTK_WINDOW(window), "AlsaTune");
    gtk_window_set_default_size(GTK_WINDOW(window), 640, 510);
    gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
    g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);

    // Main vertical box to hold both mixer and EQ sections
    GtkWidget *main_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5);
    gtk_container_set_border_width(GTK_CONTAINER(main_box), 5);
    gtk_container_add(GTK_CONTAINER(window), main_box);

    // Mixer frame with border
    GtkWidget *mixer_frame = gtk_frame_new(NULL);
    gtk_box_pack_start(GTK_BOX(main_box), mixer_frame, FALSE, FALSE, 0);

    // Mixer horizontal box
    GtkWidget *mixer_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 10);
    gtk_container_set_border_width(GTK_CONTAINER(mixer_box), 5);
    gtk_container_add(GTK_CONTAINER(mixer_frame), mixer_box);

    // Add sliders for each mixer channel
    for (int i = 0; i < mixer_data.num_channels; i++) {
        GtkWidget *channel_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5);
        gtk_box_pack_start(GTK_BOX(mixer_box), channel_box, TRUE, TRUE, 5);

        GtkWidget *label = gtk_label_new(mixer_data.channels[i].channel_name);
        gtk_widget_set_halign(label, GTK_ALIGN_CENTER);
        gtk_box_pack_start(GTK_BOX(channel_box), label, FALSE, FALSE, 5);

        mixer_data.channels[i].scale = gtk_scale_new_with_range(GTK_ORIENTATION_VERTICAL, 0, 1, 0.01);
        gtk_range_set_inverted(GTK_RANGE(mixer_data.channels[i].scale), TRUE);
        gtk_scale_set_draw_value(GTK_SCALE(mixer_data.channels[i].scale), TRUE);
        gtk_scale_set_value_pos(GTK_SCALE(mixer_data.channels[i].scale), GTK_POS_BOTTOM);
        gtk_widget_set_size_request(mixer_data.channels[i].scale, -1, 150);
        gtk_box_pack_start(GTK_BOX(channel_box), mixer_data.channels[i].scale, TRUE, TRUE, 0);
        g_signal_connect(mixer_data.channels[i].scale, "value-changed", G_CALLBACK(slider_changed), &mixer_data.channels[i]);

        long min, max, value;
        snd_mixer_selem_get_playback_volume_range(mixer_data.channels[i].elem, &min, &max);
        snd_mixer_selem_get_playback_volume(mixer_data.channels[i].elem, 0, &value);
        gtk_range_set_value(GTK_RANGE(mixer_data.channels[i].scale), (double)(value - min) / (max - min));

        GtkWidget *padding = gtk_label_new("");
        gtk_box_pack_start(GTK_BOX(channel_box), padding, FALSE, FALSE, 5);
    }

    // EQ frame with border
    GtkWidget *eq_frame = gtk_frame_new(NULL);
    gtk_box_pack_start(GTK_BOX(main_box), eq_frame, FALSE, FALSE, 0);

    // EQ horizontal box
    GtkWidget *eq_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 10);
    gtk_container_set_border_width(GTK_CONTAINER(eq_box), 2);
    gtk_widget_set_size_request(eq_box, -1, 200);
    gtk_container_add(GTK_CONTAINER(eq_frame), eq_box);

    // Add sliders for each EQ band
    for (int i = 0; i < eq_data.num_bands; i++) {
        if (!eq_data.bands[i].val) {
            fprintf(stderr, "Warning: NULL val for band %d: %s\n", i, eq_data.bands[i].band_name);
            continue;
        }

        GtkWidget *band_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5);
        gtk_box_pack_start(GTK_BOX(eq_box), band_box, TRUE, TRUE, 5);

        // Use shorter labels (e.g., "31 Hz" or "1 kHz")
        char short_label[16];
        const char *freq_start = eq_data.bands[i].band_name + 4;
        const char *end = strstr(freq_start, " Playback Volume");
        if (end) {
            strncpy(short_label, freq_start, end - freq_start);
            short_label[end - freq_start] = '\0';
        } else {
            strncpy(short_label, freq_start, sizeof(short_label) - 1);
            short_label[sizeof(short_label) - 1] = '\0';
        }
        // Strip existing Hz or kHz
        char *unit = strstr(short_label, " Hz");
        if (!unit) unit = strstr(short_label, " kHz");
        if (unit) *unit = '\0';
        // Append correct unit based on frequency
        if (strcmp(short_label, "1") == 0 || strcmp(short_label, "2") == 0 || strcmp(short_label, "4") == 0 || strcmp(short_label, "8") == 0 || strcmp(short_label, "16") == 0) {
            strcat(short_label, " kHz");
        } else {
            strcat(short_label, " Hz");
        }
        GtkWidget *label = gtk_label_new(short_label);
        gtk_widget_set_halign(label, GTK_ALIGN_CENTER);
        gtk_box_pack_start(GTK_BOX(band_box), label, FALSE, FALSE, 5);

        eq_data.bands[i].scale = gtk_scale_new_with_range(GTK_ORIENTATION_VERTICAL, 0, 1, 0.01);
        gtk_range_set_inverted(GTK_RANGE(eq_data.bands[i].scale), TRUE);
        gtk_scale_set_draw_value(GTK_SCALE(eq_data.bands[i].scale), TRUE);
        gtk_scale_set_value_pos(GTK_SCALE(eq_data.bands[i].scale), GTK_POS_BOTTOM);
        gtk_widget_set_size_request(eq_data.bands[i].scale, -1, 150);
        gtk_box_pack_start(GTK_BOX(band_box), eq_data.bands[i].scale, TRUE, TRUE, 0);
        g_signal_connect(eq_data.bands[i].scale, "value-changed", G_CALLBACK(eq_slider_changed), &eq_data.bands[i]);

        long value = snd_ctl_elem_value_get_integer(eq_data.bands[i].val, 0);
        gtk_range_set_value(GTK_RANGE(eq_data.bands[i].scale), (double)value / 100.0);

        GtkWidget *padding = gtk_label_new("");
        gtk_box_pack_start(GTK_BOX(band_box), padding, FALSE, FALSE, 5);
    }

    // Preset controls: entry, save button, and combo box
    GtkWidget *preset_box = gtk_grid_new();
    gtk_grid_set_column_spacing(GTK_GRID(preset_box), 5);
    gtk_widget_set_halign(preset_box, GTK_ALIGN_CENTER);
    gtk_box_pack_start(GTK_BOX(main_box), preset_box, FALSE, FALSE, 1);

    GtkWidget *preset_entry = gtk_entry_new();
    gtk_entry_set_placeholder_text(GTK_ENTRY(preset_entry), "Enter name for new pre-set");
    gtk_widget_set_size_request(preset_entry, 250, -1);
    gtk_grid_attach(GTK_GRID(preset_box), preset_entry, 1, 0, 1, 1);

    GtkWidget *save_button = gtk_button_new_with_label("Save Pre-set");
    gtk_grid_attach(GTK_GRID(preset_box), save_button, 2, 0, 1, 1);

    GtkWidget *preset_combo = gtk_combo_box_text_new();
    gtk_widget_set_size_request(preset_combo, 250, -1);
    gtk_grid_attach(GTK_GRID(preset_box), preset_combo, 3, 0, 1, 1);

    // Add spacers for equal left/right margins
    GtkWidget *spacer_left = gtk_label_new("");
    gtk_grid_attach(GTK_GRID(preset_box), spacer_left, 0, 0, 1, 1);
    gtk_widget_set_hexpand(spacer_left, TRUE);
    GtkWidget *spacer_right = gtk_label_new("");
    gtk_grid_attach(GTK_GRID(preset_box), spacer_right, 4, 0, 1, 1);
    gtk_widget_set_hexpand(spacer_right, TRUE);

    // Store references for callbacks
    g_object_set_data(G_OBJECT(save_button), "preset_entry", preset_entry);
    g_object_set_data(G_OBJECT(save_button), "preset_combo", preset_combo);
    g_object_set_data(G_OBJECT(save_button), "window", window);

    // Connect signals
    g_signal_connect(save_button, "clicked", G_CALLBACK(save_preset), &eq_data);
    g_signal_connect(preset_combo, "changed", G_CALLBACK(apply_preset), &eq_data);

    // Load existing presets into combo box
    load_presets(GTK_COMBO_BOX_TEXT(preset_combo));

    gtk_widget_show_all(window);
    gtk_main();

    cleanup_alsa(&mixer_data, &eq_data);
    return 0;
}

https://sourceforge.net/projects/vuu-do/ New Vuu-do isos uploaded April 2025!
Vuu-do GNU/Linux, minimal Devuan-based Openbox and Mate systems to build on. Also a max version for OB.
Devuan 5 mate-mini iso, pure Devuan, 100% no-vuu-do. wink Devuan 6 version also available for testing.
Please donate to support Devuan and init freedom! https://devuan.org/os/donate

Offline

#28 2025-07-12 11:09:53

tux_99
Member
Registered: 2025-06-17
Posts: 19  

Re: ALSA-only purists: Question, new GUI app for the mixer and EQ?

@greenjeans while I currently don't have a system without pulseaudio (unfortunately it's installed by default in practically every distro and AFAIK firefox requires it for sound) I tried compiling your source code in a manjaro VM (to test it in a different distro than the one you presumably developed it on). It built and run without any problems:

[testuser1@manajro-vm tmp]$ gcc -o mxeq mxeq.c $(pkg-config --cflags --libs gtk+-3.0 alsa glib-2.0) -Wall
[testuser1@manajro-vm tmp]$ ls -la
total 72
drwxr-xr-x  2 testuser1 testuser1  4096 Jul  9 19:05 ./
drwx------ 33 testuser1 testuser1  4096 Jul  9 19:04 ../
-rwx------  1 testuser1 testuser1 42808 Jul  9 19:05 mxeq*
-rw-------  1 testuser1 testuser1 20199 Jul  9 19:04 mxeq.c
[testuser1@manajro-vm tmp]$

I ran it too, but as you said with PA active all I got was a single volume slider as expected.

Nice job, it might come handy for me if I decide to run Devuan without Pulseaudio (need to investigate first how to keep firefox audio working without PA).

Last edited by tux_99 (2025-07-12 11:13:23)

Offline

#29 2025-07-12 13:03:17

stargate-sg1-cheyenne-mtn
Member
Registered: 2023-11-27
Posts: 322  

Re: ALSA-only purists: Question, new GUI app for the mixer and EQ?

^^^@tux_99

Got sound in Firefox with only alsa and JACK!

https://dev1galaxy.org/viewtopic.php?id=5568


Be Excellent to each other and Party On!
https://www.youtube.com/watch?v=rph_1DODXDU
https://en.wikipedia.org/wiki/Bill_%26_Ted%27s_Excellent_Adventure
Do unto others as you would have them do instantaneously back to you!

Offline

#30 2025-07-12 14:42:15

greenjeans
Member
Registered: 2017-04-07
Posts: 1,019  
Website

Re: ALSA-only purists: Question, new GUI app for the mixer and EQ?

^^Yeah I got sound in FF just using alsa-only, even in my Mate systems which throw a fit when you first rip out Pulse, lol.

@tux_99 Thanks so much for testing!

Last edited by greenjeans (2025-07-12 14:43:26)


https://sourceforge.net/projects/vuu-do/ New Vuu-do isos uploaded April 2025!
Vuu-do GNU/Linux, minimal Devuan-based Openbox and Mate systems to build on. Also a max version for OB.
Devuan 5 mate-mini iso, pure Devuan, 100% no-vuu-do. wink Devuan 6 version also available for testing.
Please donate to support Devuan and init freedom! https://devuan.org/os/donate

Offline

#31 Yesterday 22:11:14

aitor
Member
From: basque country
Registered: 2016-12-03
Posts: 264  
Website

Re: ALSA-only purists: Question, new GUI app for the mixer and EQ?

Thanks for your work, greenjeans!


If you work systematically, things will come by itself (Lev D. Landau)

Offline

#32 Yesterday 23:05:50

greenjeans
Member
Registered: 2017-04-07
Posts: 1,019  
Website

Re: ALSA-only purists: Question, new GUI app for the mixer and EQ?

^^ Thanks my friend!! I posted all this stuff i've been doing up to my sourceforge account, so anybody can test 'em and mess with 'em if they like. wink


https://sourceforge.net/projects/vuu-do/ New Vuu-do isos uploaded April 2025!
Vuu-do GNU/Linux, minimal Devuan-based Openbox and Mate systems to build on. Also a max version for OB.
Devuan 5 mate-mini iso, pure Devuan, 100% no-vuu-do. wink Devuan 6 version also available for testing.
Please donate to support Devuan and init freedom! https://devuan.org/os/donate

Offline

Board footer