https://github.com/williamh/espeakup/pull/19

commit 53665b8eabaefeb0f067ba536295515ceec2f9c6
Author: Samuel Thibault <samuel.thibault@ens-lyon.org>
Date:   Wed Apr 29 02:25:54 2020 +0200

    Support setting ALSA volume in addition to espeak volume
    
    This allows to make sure to reach the maximum volume permitted by the
    hardware. This makes the default volume (5) set ALSA volume to 80%, like
    the default ALSA scripts do.
    
    This is not enabled by default, since users will probably want to use a
    mixer to fine-tune their volume. But for e.g. installation images, this
    allows to spare the use of a mixer and just use the speakup volume
    control.

diff --git a/Makefile b/Makefile
index ad67b61..3cbb608 100644
--- a/Makefile
+++ b/Makefile
@@ -6,7 +6,7 @@ DEPFLAGS = -MMD
 WARNFLAGS = -Wall
 CFLAGS += ${DEPFLAGS} ${WARNFLAGS}
 
-LDLIBS = -lespeak -lpthread
+LDLIBS = -lespeak -lpthread -lasound -lm
 
 INSTALL = install
 BINMODE = 0755
diff --git a/cli.c b/cli.c
index 31db4b0..b8a6543 100644
--- a/cli.c
+++ b/cli.c
@@ -30,11 +30,15 @@ extern char *pidPath;
 /* default voice */
 extern char *defaultVoice;
 
+/* Whether to drive ALSA volume */
+extern int alsaVolume;
+
 /* command line options */
 const char *shortOptions = "P:V:adhv";
 const struct option longOptions[] = {
 	{"pid-path", required_argument, NULL, 'P'},
 	{"default-voice", required_argument, NULL, 'V'},
+	{"alsa-volume", no_argument, &alsaVolume, 1},
 	{"acsint", no_argument, NULL, 'a'},
 	{"debug", no_argument, NULL, 'd'},
 	{"help", no_argument, NULL, 'h'},
@@ -48,6 +52,7 @@ static void show_help()
 	printf("Options are as follows:\n");
 	printf("  --pid-path=path, -P path\t\tSet path for pid file.\n");
 	printf("  --default-voice=voice, -V voice\tSet default voice.\n");
+	printf("  --alsa-volume\t\t\t\tDrive the ALSA volume.\n");
 	printf("  --debug, -d\t\t\t\tDebug mode (stay in the foreground).\n");
 	printf("  --help, -h\t\t\t\tShow this help.\n");
 	printf("  --version, -v\t\t\t\tDisplay the software version.\n");
@@ -93,6 +98,7 @@ void process_cli(int argc, char **argv)
 			show_version();
 			break;
 		case -1:
+		case 0:
 			break;
 		default:
 			show_help();
diff --git a/espeak.c b/espeak.c
index 8871d1b..5d6fc96 100644
--- a/espeak.c
+++ b/espeak.c
@@ -22,6 +22,8 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <alsa/asoundlib.h>
+#include <math.h>
 
 #include "espeakup.h"
 
@@ -31,6 +33,7 @@ const int defaultPitch = 5;
 const int defaultRate = 2;
 const int defaultVolume = 5;
 char *defaultVoice = NULL;
+int alsaVolume = 0;
 
 /* multipliers and offsets */
 const int frequencyMultiplier = 11;
@@ -135,6 +138,86 @@ static espeak_ERROR set_voice(struct synth_t *s, char *voice)
 	return rc;
 }
 
+static void set_alsa_volume(int vol)
+{
+	snd_mixer_t *m;
+	snd_mixer_elem_t *e;
+	int err;
+
+	err = snd_mixer_open(&m, 0);
+	if (err < 0)
+	{
+		fprintf(stderr, "ALSA mixer open error: %s\n", snd_strerror(err));
+		return;
+	}
+
+	err = snd_mixer_attach(m, "default");
+	if (err < 0)
+	{
+		fprintf(stderr, "ALSA mixer attach error: %s\n", snd_strerror(err));
+		return;
+	}
+	err = snd_mixer_selem_register(m, NULL, NULL);
+	if (err < 0)
+	{
+		fprintf(stderr, "ALSA mixer load error: %s\n", snd_strerror(err));
+		return;
+	}
+	err = snd_mixer_load(m);
+	if (err < 0)
+       	{
+		fprintf(stderr, "ALSA mixer load error: %s\n", snd_strerror(err));
+		return;
+	}
+
+	/* Turn vol value to volume %.
+	 * We do not want to soften that much with ALSA, espeak is already
+	 * doing it. We want the default value (5) to be the usual default
+	 * volume (80%), and make higher values increase ALSA volume, up to
+	 * 100%. */
+
+	int volume = (vol+1) * 50 / 10 + 50;
+
+	for (e = snd_mixer_first_elem(m); e; e = snd_mixer_elem_next(e))
+	{
+		if (snd_mixer_elem_get_type(e) != SND_MIXER_ELEM_SIMPLE)
+			continue;
+		if (snd_mixer_selem_is_enumerated(e))
+			continue;
+
+		if (snd_mixer_selem_has_playback_switch(e)) {
+			snd_mixer_selem_set_playback_switch_all(e, 1);
+		}
+
+		if (snd_mixer_selem_has_playback_volume(e)) {
+			long min, max, set;
+
+			err = snd_mixer_selem_get_playback_dB_range(e, &min, &max);
+			if (err == 0 && min < max) {
+				if (max - min < 2400) {
+					/* 24dB amplitude is too small for using a logscale */
+					set = min + volume * (max-min) / 100;
+				} else {
+					/* Use a logscale */
+					double volf = volume / 100.;
+					if (min != SND_CTL_TLV_DB_GAIN_MUTE)
+					{
+						double minf = pow(10, (min-max) / 6000.);
+						volf = volf * (1 - minf) + minf;
+					}
+					set = 6000. * log10(volf) + max;
+				}
+				snd_mixer_selem_set_playback_dB_all(e, set, 0);
+			} else {
+				/* No dB setting, try a linear scale */
+				snd_mixer_selem_get_playback_volume_range(e, &min, &max);
+				set = min + volume * (max-min) / 100;
+				snd_mixer_selem_set_playback_volume_all(e, set);
+			}
+		}
+	}
+}
+
 static espeak_ERROR set_volume(struct synth_t *s, int vol,
 							   enum adjust_t adj)
 {
@@ -147,7 +230,11 @@ static espeak_ERROR set_volume(struct synth_t *s, int vol,
 	rc = espeak_SetParameter(espeakVOLUME, (vol + 1) * volumeMultiplier,
 							 0);
 	if (rc == EE_OK)
+	{
 		s->volume = vol;
+		if (alsaVolume)
+			set_alsa_volume(vol);
+	}
 
 	return rc;
 }
