You are not logged in.
^^^Good points! But that's the neat thing about Imagemagick, the command in the above script is just very basic using IM's default settings, but it only takes a few other small commands to do it in a different fashion. Just adding -strip to the above command takes out profiles and comments and such. And the default setting in IM already is to compress by optimizing the Huffman table.
Most image re-sizing programs i've seen are just frontends for IM. ![]()
One useful thing xnview currently lacks is quick batch optimization of JPEGs from the thumbnail view. But this can still be done via the command line as jpegoptim is in the Debian repos
Or even simpler:
#!/bin/sh
sizebox=$(yad --fixed --borders=10 --window-icon=image --form --title="Resize Image" --field="Resize to:" --width=400 --text-align=center --text="Enter new size (W x H in pixels, i.e. 800x600, 1024x768 etc.)")
size=$(echo $sizebox | awk 'BEGIN {FS="|" } { print $1 }')
if [ -z "$size" ]; then
exit 0
fi
for file in "$@"; do
convert "$file" -resize "$size" -set filename:copy '%d/%t-%wx%h' '%[filename:copy].jpg'
doneCombined with a .desktop in your file-manager in the right place:
[Desktop Entry]
Type=Action
Name=Resize Image
Comment=Make a re-sized copy of image
Icon=edit-cut
Profiles=resize;
[X-Action-Profile resize]
Exec=sh -c "cd %d && shrink2 %F"
MimeTypes=image/bmp;image/jpeg;image/png;image/webp;image/heic;image/heif;image/avif;And now you can just select in the file-manager and right-click. This makes for tiny file sizes and default output is jpg. Easy enough to tweak settings to change any of it.
It will do singles, and it will do batches as long as they're the same format, wouldn't be too hard to make it do mixed batches too though, i'm just lazy lol. ![]()
Just noticed: is there any possibility to adjust the EQ for the right and left channel seperately? That would open the possibility to adjust it according to the hearing curves which in my case are really different.
That's a great question! It can be done in the mixer for sure, and I haven't seen it done or tried it myself but I would think it's absolutely possible in the EQ as well.
Unfortunately both are beyond the scope of this simple utility project, at least for the present and forseeable future. Something like that would need it's own branch as I want to keep the main project an uber-simple drop-in base system. But again it's a great question...i'll do some thinking on it, thanks! ![]()
Exotic non-essential function that nobody really uses and tests?
Yep. Have never used it. In general I don't like the idea of clicking external links in programs that want to connect me to gawd-knows-what.
So when I double click the deb file I get a message saying it requires the installation of 115 additional packages.
Are you sure you got the right version? For Debian 13?
Great job!
You may need to make a minor change in the README.txt file for the path to .asoundrc file, so that it reads /usr/share/alsatune instead of /usr/share/mxeq.
@ilargi
Thank you! And arrrgh, lol, you're right of course...I swear, if I had a dollar for every time I rolled up something and forgot to update all the documentation...seriously, when my wife hears me say i'm about to squash a new Vuu-do iso she always asks if i've updated the release notes. ![]()
Will fix that asap. Working on Vuu-do updates this week and i'll get all that sorted too. EDIT: It was bugging me, had another couple spots I hadn't completed the conversion to alsatune as well, so I fixed it and re-packaged and uploaded. Thanks for catching that!!
@Calamity, thank you! There are some nice mixers out there for alsa, just saw a new project somebody did in QT for the mixer and it's really nice and very complete with some features power-users and audiophiles might appreciate. Mine is a little more simple and pedestrian, but it should get the job done for most folks.
New version 3.0-0 of Alsatune uploaded, new features and I changed the name of the package and main script to be "alsatune", no more potential conflicts. So it's ready for testing.
I have moved the source files for the two C scripts out of the package, and instead they are in a tar.xz source file that is also on the site for anyone who wants to check out the source code.
If you want a copy of this, grab it now, i'll be taking both versions down from Sourceforge in the morning.
(versions 1 and 2, naming conflict).
My take on it for what it's worth, i'm fond of analogies as like memes they allow us to impart more info with less words.
I spent many years in various types of construction and fabrication, so that's the basis for this analogy.
Devuan is not a homebuilder, Devuan is Home Depot, with tens of thousands of building materials and tools created by a diverse group of folks worldwide, they provide a range of products to cover almost any need, while also trying to filter out the bad products that try to sneak in.
I, as a person creating a distro, am the homebuilder. It is my job to be architect, designer, general contractor, carpenter, plumber, electrician, and to provide a finished ready-to-move-in home.
That's as simple as I can make it, to me Devuan is Home Depot, and quite a nice one at that. Bonus: I don't have to pay for the materials or tools. I go shopping and come home with a trailer-load of cool stuff and am only limited by my own skills and creativity.
cumbersome, bloated
@wert, bro, the deb package for the current 2.1 version is 21.8 kb,and that includes the main script, the helper script, a giant readme, the entirety of the source code, a .desktop, an asoundrc file, and a test tone .wav file that's larger than the main script itself.... in what world is that considered "bloated"??
Had alsamixer and alsamixer -D equal in the menu but replaced both with your app... polished, easy to use and presets. Thanks greenjeans!
Thank you, you just made my day! Yep I did the same thing for a long time, menu entries to open the terminal versions...then it just became another itch I couldn't scratch and I felt I could make something better.
I guess you won't be interested but as you may know, the action path for Devuan would be for a maintainer to set up a fork project (at git.devuan.org) to sort out its utility in Devuan. I anyhow invite you to take on that role for this package (as well as fftrate packages if you like).
I agree 100%, I think Igor would be the perfect candidate to maintain packages like fftrate. He's certainly knowledgeable about all things ALSA, and you've already done the hard part for fftrate.
C'mon Igor, step up to the plate bro! Ralph's rolling out the red carpet for ya and Devuan is clearly onboard with all this amazing work being done in ALSA these days, we can all make a real difference in how sound is dealt with in Linux. ![]()
I typically use the package system-config-printer, it's a nice little gui for helping get set up, and works fine in my experience with hplip and all the other backend drivers and cups stuff.
If it's a network printer as opposed to direct connect, cups-browsed might help.
however, it would not recognize the usb-stick for when writing iso, however it would recognize it when trying to format usb-stick?
Wow, never had that problem with it, weird. It's no exaggeration to say i've used it 1000 times in the last year, and never once had a problem. In fact I made a helper app to go with it, to add a fat32 data partition to the stick after installing the iso.
Possibly you're missing a dependency or maybe even a recommend? Let me know how it turns out please!
And what I have seen is inherited from Debian upstream.
^^THIS^^. And not just Debian but further upstream too.
I'm pretty blown away at all the work the devs have had to do (and are still doing) to make it work at all. Bless them all for doing it too, with no pay and not a lot of gratitude shown by some.
At some point it's time to stop walking away from things that aren't working so well, and jump into the fray and start helping.
Once more unto the breech dear friends. ![]()
too much gtk and too little alsa
that gtk is going to kill you.
You are sooooo right!!! Not just gtk though, css is wearing me out too. Those two things cost me several hours of flailing about the last two days. arrrgh.
#include <wayland-client-protocol.h>Ummm...no....just...no. Not happening ever.
Great job. Have you considered adding capture? Could be handy for people using jitsi etc. I couldn't figure out why the mic wasn't working and it turned out capture was not enabled.
Hey buddy! Yeah I did actually, the script now scans for all channels on the backend including capture, got a nice little slider for it even on my ancient laptop.
The latest 2.1 and 3.0 scripts and now also the alsatoggle script, poll the backend every time you start them up and pull everything that exists and use that to populate the gui, so even if you change the physical sound card on a given system, the next time you open AlsaTune (or alsatoggle) all your new stuff will be what's there.
While watching the changes in alsamixer, it occured to me that there needs to be a way to un-mute channels in AlsaTune. If a channel is muted, the slider won't do anything and there's no way to know that without opening alsamixer. (or did I miss something?)
You did. Right-click any one of the sliders themselves in AlsaTune.
You know it's muted if the slider turns red/black striped. Check the screenie on page 3 towards the bottom.
I mentioned this in my last post, in the 2.1 version muting/unmuting is enabled via right-click on the sliders themselves.
But I never made it clear in the gui itself that you could do that, my bad, so I added a tooltip just for the sliders that have a mute function in version 3 that i'm working on, that let's you know you can right-click to mute.
Really appreciate you testing!!! It's hard with just a couple machines and those all being old,low spec, and basically all the same lol. I'm sure there's more tweaks to be done in the future. But after a couple long days working the new version i'm really happy with the progress.
Got it all essentially done, I added one more feature after adding the new button/script, I added a tooltip that only appears when you hover over volume sliders that have a mute function, to let you know that you can right-click to mute/unmute, it's a small thing but there was no clear instruction about that in the main gui.
So for the new script, it would be so amazing if some of you smart folks could take a minute and test function. It's a complete standalone script so you don't need AlsaTune to run it, just compile it and run. I'm especially interested in any new toggles it might miss, and whether it gives a proper description on common toggles or falls back to the default because of a new name.
You will need gcc of course and all that it pulls in, and you will also need the libgtk3-dev and libasound2-dev packages.
Compile:
gcc alsatoggle.c -o alsatoggle $(pkg-config --cflags --libs gtk+-3.0) -lasoundalsatoggle.c
#include <gtk/gtk.h>
#include <alsa/asoundlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
typedef struct {
snd_mixer_elem_t *elem;
} ToggleChannel;
typedef struct {
snd_mixer_t *mixer;
ToggleChannel *channels;
int num_channels;
GtkListStore *store;
} ToggleData;
// Normalize name for matching (lowercase, spaces/dashes removed)
static void normalize_name(char *dest, const char *src, size_t dest_size) {
size_t i = 0;
while (*src && i < dest_size - 1) {
char c = tolower(*src);
if (c != ' ' && c != '-') {
dest[i++] = c;
}
src++;
}
dest[i] = '\0';
}
// Get user-friendly description, this section will need fine-tuning and additions
static const char* get_description(const char *alsa_name) {
char normalized[128];
normalize_name(normalized, alsa_name, sizeof(normalized));
if (strstr(normalized, "automute") ||
strstr(normalized, "automutemode") ||
strstr(normalized, "automode")) {
return "Automatically mutes internal speakers when headphones are plugged in.";
}
else if (strstr(normalized, "loopback")) {
return "Routes audio output back to input (for recording what you hear).";
}
else if (strstr(normalized, "independenthp") ||
strstr(normalized, "independentheadphone")) {
return "Allows independent audio streams for headphones and speakers (no auto-muting of speakers).";
}
else if (strstr(normalized, "beep")) {
return "Controls the PC speaker beep tone (system alerts). Usually best left disabled to avoid noise.";
}
else if (strstr(normalized, "phantom") ||
strstr(normalized, "48v")) {
return "Provides 48V phantom power for condenser microphones.";
}
return "Toggles this audio control on or off.";
}
// Callback when a toggle is flipped
static void on_toggle_changed(GtkCellRendererToggle *renderer, gchar *path_str, ToggleData *data) {
GtkTreeIter iter;
if (!gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(data->store), &iter, path_str))
return;
gboolean active;
gtk_tree_model_get(GTK_TREE_MODEL(data->store), &iter, 0, &active, -1);
active = !active; // Flip it
int row = atoi(path_str);
snd_mixer_elem_t *elem = data->channels[row].elem;
// Apply to ALSA
if (snd_mixer_selem_has_playback_switch(elem)) {
snd_mixer_selem_set_playback_switch_all(elem, active);
}
if (snd_mixer_selem_has_capture_switch(elem)) {
snd_mixer_selem_set_capture_switch_all(elem, active);
}
if (snd_mixer_selem_is_enumerated(elem)) {
unsigned int item = active ? 1 : 0; // Most are 0=Disabled, 1=Enabled
snd_mixer_selem_set_enum_item(elem, 0, item);
}
// Update the store/UI
gtk_list_store_set(data->store, &iter, 0, active, -1);
}
static gboolean on_row_click(GtkWidget *treeview, GdkEventButton *event, ToggleData *data) {
if (event->button == 3) { // Right-click toggle
GtkTreePath *path;
if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(treeview), event->x, event->y, &path, NULL, NULL, NULL)) {
GtkTreeIter iter;
if (gtk_tree_model_get_iter(GTK_TREE_MODEL(data->store), &iter, path)) {
gboolean active;
gtk_tree_model_get(GTK_TREE_MODEL(data->store), &iter, 0, &active, -1);
active = !active;
gtk_list_store_set(data->store, &iter, 0, active, -1);
int row = gtk_tree_path_get_indices(path)[0];
snd_mixer_elem_t *elem = data->channels[row].elem;
// Same apply code as on_toggle_changed
if (snd_mixer_selem_has_playback_switch(elem)) {
snd_mixer_selem_set_playback_switch_all(elem, active);
}
if (snd_mixer_selem_has_capture_switch(elem)) {
snd_mixer_selem_set_capture_switch_all(elem, active);
}
if (snd_mixer_selem_is_enumerated(elem)) {
unsigned int item = active ? 1 : 0;
snd_mixer_selem_set_enum_item(elem, 0, item);
}
}
gtk_tree_path_free(path);
return TRUE; // Handled
}
}
return FALSE; // Pass left-click through
}
// Initialize ALSA and find toggles
static void init_alsa_toggles(ToggleData *data) {
data->mixer = NULL;
data->channels = NULL;
data->num_channels = 0;
if (snd_mixer_open(&data->mixer, 0) < 0) return;
if (snd_mixer_attach(data->mixer, "default") < 0 ||
snd_mixer_selem_register(data->mixer, NULL, NULL) < 0 ||
snd_mixer_load(data->mixer) < 0) {
snd_mixer_close(data->mixer);
return;
}
ToggleChannel temp[64];
int count = 0;
snd_mixer_elem_t *elem;
for (elem = snd_mixer_first_elem(data->mixer); elem; elem = snd_mixer_elem_next(elem)) {
if (!snd_mixer_selem_is_active(elem)) continue;
gboolean has_volume = snd_mixer_selem_has_playback_volume(elem) ||
snd_mixer_selem_has_capture_volume(elem);
gboolean has_switch = snd_mixer_selem_has_playback_switch(elem) ||
snd_mixer_selem_has_capture_switch(elem);
gboolean is_enum = snd_mixer_selem_is_enumerated(elem);
// Pure toggle: either a switch without volume, or an enumerated control
if ((has_switch && !has_volume) || is_enum) {
if (count >= 64) break;
temp[count].elem = elem;
count++;
}
}
if (count > 0) {
data->num_channels = count;
data->channels = g_new(ToggleChannel, count);
memcpy(data->channels, temp, count * sizeof(ToggleChannel));
}
}
static void cleanup_alsa(ToggleData *data) {
if (data->mixer) snd_mixer_close(data->mixer);
g_free(data->channels);
}
int main(int argc, char *argv[]) {
gtk_init(&argc, &argv);
gtk_window_set_default_icon_name("audio-speakers");
ToggleData data = {0};
init_alsa_toggles(&data);
GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_title(GTK_WINDOW(window), "AlsaTune - Toggle Functions");
gtk_window_set_default_size(GTK_WINDOW(window), 660, 400);
gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);
// Some styling to match main app
GtkCssProvider *css = gtk_css_provider_new();
gtk_css_provider_load_from_data(css,
"box { border: 2px solid #333333; border-radius: 4px; }"
"button { border: 1px solid #333333; border-radius: 4px; }",
-1, NULL);
gtk_style_context_add_provider_for_screen(gdk_screen_get_default(),
GTK_STYLE_PROVIDER(css),
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5);
gtk_container_set_border_width(GTK_CONTAINER(vbox), 5);
gtk_container_add(GTK_CONTAINER(window), vbox);
GtkWidget *scrolled = gtk_scrolled_window_new(NULL, NULL);
gtk_box_pack_start(GTK_BOX(vbox), scrolled, TRUE, TRUE, 0);
if (data.num_channels == 0) {
GtkWidget *label = gtk_label_new("No toggle controls detected on this system.");
gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
gtk_widget_set_margin_top(label, 40);
gtk_container_add(GTK_CONTAINER(scrolled), label);
} else {
GtkWidget *treeview = gtk_tree_view_new();
GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview));
gtk_tree_selection_set_mode(selection, GTK_SELECTION_NONE);
gtk_container_add(GTK_CONTAINER(scrolled), treeview);
gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(treeview), FALSE);
// Column 1: Toggle switch
GtkCellRenderer *rend_toggle = gtk_cell_renderer_toggle_new();
GtkTreeViewColumn *col_toggle = gtk_tree_view_column_new_with_attributes("", rend_toggle, "active", 0, NULL);
gtk_tree_view_column_set_fixed_width(col_toggle, 80);
gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), col_toggle);
// Column 2: Name
GtkCellRenderer *rend_name = gtk_cell_renderer_text_new();
g_object_set(rend_name, "weight", PANGO_WEIGHT_BOLD, "xpad", 10, NULL);
GtkTreeViewColumn *col_name = gtk_tree_view_column_new_with_attributes("Control", rend_name, "text", 1, NULL);
gtk_tree_view_column_set_min_width(col_name, 200);
gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), col_name);
// Column 3: Description (wraps and expands)
GtkCellRenderer *rend_desc = gtk_cell_renderer_text_new();
g_object_set(rend_desc, "wrap-mode", PANGO_WRAP_WORD, "wrap-width", 330, NULL);
GtkTreeViewColumn *col_desc = gtk_tree_view_column_new_with_attributes("Description", rend_desc, "text", 2, NULL);
gtk_tree_view_column_set_expand(col_desc, TRUE);
gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), col_desc);
// ListStore: active (bool), name (string), description (string)
data.store = gtk_list_store_new(3, G_TYPE_BOOLEAN, G_TYPE_STRING, G_TYPE_STRING);
gtk_tree_view_set_model(GTK_TREE_VIEW(treeview), GTK_TREE_MODEL(data.store));
for (int i = 0; i < data.num_channels; i++) {
snd_mixer_elem_t *elem = data.channels[i].elem;
const char *name = snd_mixer_selem_get_name(elem);
const char *desc = get_description(name);
// Determine current state
gboolean active = FALSE;
if (snd_mixer_selem_has_playback_switch(elem)) {
int val;
snd_mixer_selem_get_playback_switch(elem, SND_MIXER_SCHN_FRONT_LEFT, &val);
active = val;
} else if (snd_mixer_selem_is_enumerated(elem)) {
unsigned int item;
snd_mixer_selem_get_enum_item(elem, 0, &item);
active = (item == 1); // Adjust if any are reversed
}
GtkTreeIter iter;
gtk_list_store_append(data.store, &iter);
gtk_list_store_set(data.store, &iter,
0, active,
1, name,
2, desc,
-1);
}
g_signal_connect(rend_toggle, "toggled", G_CALLBACK(on_toggle_changed), &data);
g_signal_connect(treeview, "button-press-event", G_CALLBACK(on_row_click), &data);
}
// Big goofy close button at bottom
GtkWidget *close_btn = gtk_button_new_with_label("To enable a function, check the box beside it or hover over its row and right-click."
"\nUncheck or click again to disable. Left-click here to exit this window when done.");
gtk_widget_set_tooltip_text(close_btn,
"Click this button (or the window's X) to close when you're done. "
"All changes are applied instantly and saved on the backend, no further action needed.");
gtk_widget_set_halign(close_btn, GTK_ALIGN_CENTER);
gtk_widget_set_margin_top(close_btn, 10);
gtk_widget_set_margin_bottom(close_btn, 10);
g_signal_connect_swapped(close_btn, "clicked", G_CALLBACK(gtk_widget_destroy), window);
gtk_box_pack_start(GTK_BOX(vbox), close_btn, FALSE, FALSE, 0);
gtk_widget_show_all(window);
gtk_main();
cleanup_alsa(&data);
return 0;
}Getting there....should have a version 3 of AlsaTune soon!

This is a separate script that will be called from a button on the regular AlsaTune gui and pops up it's own window. There just wasn't space on the main gui to do everything and this works a lot better, more modular so it's easier to work on and very simple. It works basically the same way as AlsaTune, polls the backend for functions that are pure on/off toggles, checkbox to indicate status, name, description. Easy and simple even for a novice to work with. All changes are instantaneously applied in real-time, you can open the alsamixer backend and watch it change while you click.
Im curious about this, ive always used below code to disable DPMS and prevent screen from blanking, what is yours doing differently?
Overkill, two of the commands are the same as yours, the first command in mine takes the extra step of turning all the values to nil before turning it off completely. (values = blank,suspend,shutdown). In certain systems/situations xset can be persnickety, this insures that if somehow the -dpms command is reversed, it will still not blank etc. because all the values are turned off.
Good analogy is a volume control like in alsamixer, it has a slider for volume but also a mute function that cuts off sound completely.
-dpms is the mute switch, and 0 0 0 is the volume sliders that we're turning all the way down.
Mintstick is what you seek my friend, it's in the repo, it's an awesome utility. Refracta2usb if you want to do more complex things like multi-boot and persistence.
That's awesome! Thanks so much for giving that a try, working on the next upgrade all day yesterday and still not done, but i've figured out how I want to deal with the toggles on the backend, and I have a working prototype now, very simple to use, very hard to make, lol.
Just an observation from what i've learned so far:
I like C, only been messing with it since earlier this year, but it's fairly easy to get the hang of, i'm enjoying it more and more. C is not really what makes things take so long, it's GTK and CSS and doing 50+ cycles of compile/test/mod/repeat just to get one f***ing element to be in the right place and look right.
me: Okay gcc, let's rock this thing, go!
gcc: Sorry bro, you used a deprecated command that's been replaced by a command that does the exact same thing but has another name.
me:
gcc:
me: **** you
gcc: **** you noob, RTFM.
Cool beans! I just now uploaded the new version 2.1-0 to Sourceforge it has the new script and some updated info:
https://sourceforge.net/projects/vuu-do … /AlsaTune/
Give it some of that Refracta magic! ![]()
Crap, lost my build-depends file, been messing with this for months, so if I remember correctly:
libasound2-dev
libgtk-3-dev
libglib2.0-dev
Compile:
gcc -o mxeq mxeq.c $(pkg-config --cflags --libs gtk+-3.0 alsa glib-2.0) -Wall