Sophie

Sophie

distrib > * > 2008.0 > x86_64 > by-pkgid > 2b471379494a2acc128492c7574ce12f > files > 27

xmms-1.2.10-35mdv2008.0.src.rpm

--- xmms-1.2.8-pre1/Input/mpg123/configure.c.rva	2003-05-19 23:22:06.000000000 +0200
+++ xmms-1.2.8-pre1/Input/mpg123/configure.c	2003-08-17 22:09:33.000000000 +0200
@@ -27,6 +27,10 @@
 static GtkWidget *streaming_proxy_hbox, *streaming_proxy_auth_hbox, *streaming_save_dirbrowser;
 static GtkWidget *streaming_save_hbox, *title_id3_box, *title_tag_desc;
 static GtkWidget *title_override, *title_id3_entry, *title_id3v2_disable;
+static GtkWidget *volume_rva2_enable, *volume_boost_enable, *volume_dither_enable;
+static GtkWidget *volume_gain_label, *volume_clip_frame;
+static GtkWidget *volume_limiter_enable, *volume_reducegain_enable;
+static gchar volume_gaintext[24];
 
 MPG123Config mpg123_cfg;
 
@@ -105,6 +109,14 @@
 	mpg123_cfg.disable_id3v2 = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(title_id3v2_disable));
 	g_free(mpg123_cfg.id3_format);
 	mpg123_cfg.id3_format = g_strdup(gtk_entry_get_text(GTK_ENTRY(title_id3_entry)));
+	mpg123_cfg.use_rva2 = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(volume_rva2_enable));
+	mpg123_cfg.use_boost = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(volume_boost_enable));
+	mpg123_cfg.enable_dither = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(volume_dither_enable));
+	if (GTK_TOGGLE_BUTTON(volume_limiter_enable)->active)
+		mpg123_cfg.anticlip_mode = 0;
+	else if (GTK_TOGGLE_BUTTON(volume_reducegain_enable)->active)
+		mpg123_cfg.anticlip_mode = 1;
+	mpg123_voladjust_update(NULL);
 
 	filename = g_strconcat(g_get_home_dir(), "/.xmms/config", NULL);
 	cfg = xmms_cfg_open_file(filename);
@@ -135,6 +147,10 @@
 	xmms_cfg_write_boolean(cfg, "MPG123", "disable_id3v2", mpg123_cfg.disable_id3v2);
 	xmms_cfg_write_string(cfg, "MPG123", "id3_format", mpg123_cfg.id3_format);
 	xmms_cfg_write_int(cfg, "MPG123", "detect_by", mpg123_cfg.detect_by);
+ 	xmms_cfg_write_boolean(cfg, "MPG123", "use_rva2", mpg123_cfg.use_rva2);
+ 	xmms_cfg_write_boolean(cfg, "MPG123", "use_boost", mpg123_cfg.use_boost);
+ 	xmms_cfg_write_boolean(cfg, "MPG123", "enable_dither", mpg123_cfg.enable_dither);
+ 	xmms_cfg_write_int(cfg, "MPG123", "anticlip_mode", mpg123_cfg.anticlip_mode);
 #ifdef USE_SIMD
 	xmms_cfg_write_int(cfg, "MPG123", "default_synth", mpg123_cfg.default_synth);
 #endif
@@ -212,6 +228,16 @@
 	gtk_widget_set_sensitive(title_tag_desc, override);
 }
 
+static void volume_rva2_cb(GtkWidget * w, gpointer data)
+{
+	gboolean rva2_on;
+	rva2_on = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(volume_rva2_enable));
+	gtk_widget_set_sensitive(volume_boost_enable, rva2_on);
+	gtk_widget_set_sensitive(volume_dither_enable, rva2_on);
+	gtk_widget_set_sensitive(volume_clip_frame, rva2_on);
+	gtk_widget_set_sensitive(volume_gain_label, rva2_on);
+}
+
 static void configure_destroy(GtkWidget * w, gpointer data)
 {
 	if (streaming_save_dirbrowser)
@@ -230,6 +256,7 @@
 	GtkWidget *streaming_save_label, *streaming_save_browse;
 	GtkWidget *streaming_cast_frame, *streaming_cast_vbox;
 	GtkWidget *title_frame, *title_id3_vbox, *title_id3_label;
+	GtkWidget *volume_frame, *volume_vbox, *volume_clip_vbox;
 	GtkWidget *bbox, *ok, *cancel;
 
 	char *temp;
@@ -578,6 +605,67 @@
 	gtk_box_pack_start(GTK_BOX(title_id3_vbox), title_tag_desc, FALSE, FALSE, 0);
 	gtk_notebook_append_page(GTK_NOTEBOOK(notebook), title_frame, gtk_label_new(_("Title")));
 
+	/*
+	 * Volume adjustment config
+	 */
+	volume_frame = gtk_frame_new(_("Volume Adjustment:"));
+	gtk_container_border_width(GTK_CONTAINER(volume_frame), 5);
+
+	volume_vbox = gtk_vbox_new(FALSE, 10);
+	gtk_container_border_width(GTK_CONTAINER(volume_vbox), 5);
+	gtk_container_add(GTK_CONTAINER(volume_frame), volume_vbox);
+
+	volume_rva2_enable = gtk_check_button_new_with_label(_("Enable ID3 relative volume adjust"));
+	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(volume_rva2_enable),
+				     mpg123_cfg.use_rva2);
+	gtk_signal_connect(GTK_OBJECT(volume_rva2_enable), "clicked", volume_rva2_cb, NULL);
+	gtk_box_pack_start(GTK_BOX(volume_vbox), volume_rva2_enable, FALSE, FALSE, 0);
+
+	volume_boost_enable = gtk_check_button_new_with_label(_("Enable 6dB boost"));
+	gtk_widget_set_sensitive(volume_boost_enable, mpg123_cfg.use_rva2);
+	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(volume_boost_enable),
+				     mpg123_cfg.use_boost);
+	gtk_box_pack_start(GTK_BOX(volume_vbox), volume_boost_enable, FALSE, FALSE, 0);
+
+	volume_dither_enable = gtk_check_button_new_with_label(_("Enable dithering"));
+	gtk_widget_set_sensitive(volume_dither_enable, mpg123_cfg.use_rva2);
+	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(volume_dither_enable),
+				     mpg123_cfg.enable_dither);
+	gtk_box_pack_start(GTK_BOX(volume_vbox), volume_dither_enable, FALSE, FALSE, 0);
+
+	volume_clip_frame = gtk_frame_new(_("Clipping Prevention:"));
+	gtk_widget_set_sensitive(volume_clip_frame, mpg123_cfg.use_rva2);
+	gtk_container_border_width(GTK_CONTAINER(volume_clip_frame), 5);
+
+	volume_clip_vbox = gtk_vbox_new(FALSE, 10);
+	gtk_container_border_width(GTK_CONTAINER(volume_clip_vbox), 5);
+	gtk_container_add(GTK_CONTAINER(volume_clip_frame), volume_clip_vbox);
+
+	volume_limiter_enable = gtk_radio_button_new_with_label(NULL, _("Use limiter"));
+	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(volume_limiter_enable),
+				     mpg123_cfg.anticlip_mode == 0);
+	gtk_box_pack_start(GTK_BOX(volume_clip_vbox), volume_limiter_enable, FALSE, FALSE, 0);
+
+	volume_reducegain_enable = gtk_radio_button_new_with_label(gtk_radio_button_group(GTK_RADIO_BUTTON(volume_limiter_enable)), _("Reduce gain"));
+	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(volume_reducegain_enable),
+				     mpg123_cfg.anticlip_mode == 1);
+	gtk_box_pack_start(GTK_BOX(volume_clip_vbox), volume_reducegain_enable, FALSE, FALSE, 0);
+
+	gtk_box_pack_start(GTK_BOX(volume_vbox), volume_clip_frame, FALSE, FALSE, 0);
+
+	if (fabs(mpg123_current_gain) < 0.0001)
+		strcpy(volume_gaintext, _("Current gain: none"));
+	else
+		g_snprintf(volume_gaintext, 24, _("Current gain: %0.2fdB"),
+			   mpg123_current_gain);
+	volume_gain_label = gtk_label_new(volume_gaintext);
+	gtk_widget_set_sensitive(volume_gain_label, mpg123_cfg.use_rva2);
+	gtk_box_pack_end(GTK_BOX(volume_vbox), volume_gain_label, FALSE, FALSE, 0);
+
+	gtk_notebook_append_page(GTK_NOTEBOOK(notebook), volume_frame, gtk_label_new(_("Volume")));
+
+
+
 	bbox = gtk_hbutton_box_new();
 	gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END);
 	gtk_button_box_set_spacing(GTK_BUTTON_BOX(bbox), 5);
--- xmms-1.2.8-pre1/Input/mpg123/id3_frame_volume.c.rva	2003-08-17 22:09:33.000000000 +0200
+++ xmms-1.2.8-pre1/Input/mpg123/id3_frame_volume.c	2003-08-17 22:09:33.000000000 +0200
@@ -0,0 +1,144 @@
+/*********************************************************************
+ * 
+ * Filename:      id3_frame_volume.c
+ * Description:   Code for handling ID3 relative volume adjust frames.
+ * Author:        Chris Vaill <cvaill@cs.columbia.edu>
+ * Created at:    Thu Sep 26 23:57:00 2002
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ * 
+ ********************************************************************/
+#include <stdio.h>
+#include <string.h>
+
+#include "id3.h"
+
+
+/* Assume buf points to the beginning of one channel's data in an RVA2
+ * frame.  Return the encoded gain, in decibels. */
+static float decode_rva2_gain(const char *buf)
+{
+    int gain_fp; /* fixed-point */
+
+    gain_fp = *(signed char *)(buf + 1) << 8; /* first byte of gain */
+    gain_fp |= *(unsigned char *)(buf + 2);   /* second byte of gain */
+
+    return gain_fp / 512.0;
+}
+
+
+/* Assume buf points to the beginning of one channel's peak data in an
+ * RVA2 frame (i.e. the fourth byte in the channel's data).  Return
+ * the encoded peak, shifted to be a 32-bit value. */
+static guint32 decode_rva2_peak(const unsigned char *buf)
+{
+    unsigned int peakbits = (unsigned int)*buf++;
+    int shift, peakbytes;
+    guint32 peak = 0;
+
+    /* fast path for the common case */
+    if (peakbits == 16)
+	return (buf[0] << 24) + (buf[1] << 16);
+
+    /*
+     * We only care about the high 32 bits, so we care about the first
+     * 5 bytes, because unfortunately, sizes that are not whole bytes
+     * are padded on the msb side.
+     *
+     * e.g. a 33 bit peak has its first 7 bits zeroed, followed by 1
+     * bit and four bytes of peak data.  We take the first four bytes
+     * and shift left by 7, giving us 25 bits total.  We then take the
+     * top 7 bits of the fifth byte to get our full 32 bits.
+     */
+    peakbytes = (peakbits + 7) >> 3;
+    if (peakbytes > 4)
+	peakbytes = 4;
+    shift = ((8 - (peakbits & 7)) & 7) + (4 - peakbytes) * 8;
+
+    /* get up to the first 4 bytes of the peak... */
+    for (; peakbytes; peakbytes--) {
+	peak <<= 8;
+	peak += (unsigned int)*buf++;
+    }
+
+    /* ...and shift, so the value is out of 32 bits... */
+    peak <<= shift;
+
+    /* ...and finally, add part of the fifth byte, if necessary */
+    if (peakbits > 32)
+	peak += (unsigned int)*buf >> (8 - shift);
+
+    return peak;
+}
+
+
+/*
+ * Function id3_get_rva2_gain (frame)
+ *
+ *    Get the gain and peak info, that is coded in the RVA2 frame, and
+ *    return it in the gn struct.  Return 0, or -1 on error.
+ */
+int id3_get_rva2_gain(id3_frame_t *frame, struct id3_gain *gn)
+{
+    const unsigned char *data;
+    float gain;
+    int i, chan, peakbytes;
+    guint32 peak;
+
+    /* Type check */
+    if (frame->fr_desc->fd_id != ID3_RVA2)
+	return -1;
+
+    /* Check if frame is compressed */
+    if (id3_decompress_frame(frame) == -1)
+	return -1;
+
+    /* init gains and peaks to 0 */
+    memset(gn, 0, sizeof(struct id3_gain));
+
+    data = (char *)frame->fr_data;
+
+    /* skip past identification string */
+    for (i = 0; i < frame->fr_size; i++)
+	if (data[i] == '\0')
+	    break;
+    if (i >= frame->fr_size)
+	return -1; /* ident string not terminated; bad frame data */
+
+    /* cycle through channel fields */
+    i++;
+    while (i + 3 < frame->fr_size) {
+
+	chan = data[i];
+
+	/* decode the gain for this channel */
+	gain = decode_rva2_gain(data + i);
+	i += 3;
+
+	/* decode the peak for this channel */
+	peakbytes = (data[i] + 7) / 8;
+	if (i + peakbytes >= frame->fr_size)
+	    break;
+	peak = decode_rva2_peak(data + i);
+	i += 1 + peakbytes;
+
+	if (chan <= 8) {
+	    gn->channel[chan].ch_gain = gain;
+	    gn->channel[chan].ch_peak = peak;
+	}
+    }
+
+    return 0;
+}
--- xmms-1.2.8-pre1/Input/mpg123/id3.h.rva	2003-06-11 20:44:17.000000000 +0200
+++ xmms-1.2.8-pre1/Input/mpg123/id3.h	2003-08-17 22:24:48.000000000 +0200
@@ -101,6 +101,8 @@
 	GList *id3_frame;
 };
 
+typedef struct id3_tag id3_t;
+
 #define ID3_TYPE_NONE	0
 #define ID3_TYPE_MEM	1
 #define ID3_TYPE_FD	2
@@ -128,6 +130,7 @@
 	int fr_size_z;		/* Size of decompressed compressed frame */
 };
 
+typedef struct id3_frame id3_frame_t;
 
 /*
  * Structure describing an ID3 frame type.
@@ -140,6 +143,40 @@
 
 
 /*
+ * Structure describing volume adjustment info in an RVA2 frame.
+ */
+struct id3_gain {
+    struct {
+
+	/* gain in decibels */
+	float ch_gain;
+
+	/* We shift peak values to be 32 bits, e.g. a 17-bit peak is
+	 * left shifted 15 (the low 15 bits are padded with zero).
+	 * This way, we can always treat the peak as being out of 32
+	 * bits.  RVA2 peak values can be up to 255 bits, but the high
+	 * 32 bits should more than suffice for us. */
+	guint32 ch_peak;
+
+    } channel[9];
+};
+
+/*
+ * These are both RVA2 channel type numbers, and indexes into the
+ * channel element of struct id3_gain.
+ */
+#define ID3_CHANNEL_OTHER   0x00
+#define ID3_CHANNEL_MASTER  0x01
+#define ID3_CHANNEL_FRIGHT  0x02
+#define ID3_CHANNEL_FLEFT   0x03
+#define ID3_CHANNEL_BRIGHT  0x04
+#define ID3_CHANNEL_BLEFT   0x05
+#define ID3_CHANNEL_FCENTER 0x06
+#define ID3_CHANNEL_BCENTER 0x07
+#define ID3_CHANNEL_SUB     0x08
+
+
+/*
  * Text encodings.
  */
 #define ID3_ENCODING_ISO_8859_1	0x00
@@ -359,6 +396,9 @@
 char *id3_get_url(struct id3_frame *);
 char *id3_get_url_desc(struct id3_frame *);
 
+/* From id3_frame_volume.c */
+int id3_get_rva2_gain(id3_frame_t *, struct id3_gain *);
+
 /* From id3_tag.c */
 void id3_init_tag(struct id3_tag *id3);
 int id3_read_tag(struct id3_tag *id3);
--- xmms-1.2.8-pre1/Input/mpg123/layer1.c.rva	2000-08-02 13:47:50.000000000 +0200
+++ xmms-1.2.8-pre1/Input/mpg123/layer1.c	2003-08-17 22:09:33.000000000 +0200
@@ -176,7 +176,10 @@
 			while (mpg123_ip.output->buffer_free() < mpg123_pcm_point && mpg123_info->going && mpg123_info->jump_to_time == -1)
 				xmms_usleep(10000);
 			if (mpg123_info->going && mpg123_info->jump_to_time == -1)
+			{
+				mpg123_voladjust(mpg123_pcm_sample, mpg123_pcm_point);
 				mpg123_ip.output->write_audio(mpg123_pcm_sample, mpg123_pcm_point);
+			}
 		}
 		mpg123_pcm_point = 0;
 	}
--- xmms-1.2.8-pre1/Input/mpg123/layer2.c.rva	2003-05-19 23:22:06.000000000 +0200
+++ xmms-1.2.8-pre1/Input/mpg123/layer2.c	2003-08-17 22:09:33.000000000 +0200
@@ -330,7 +330,10 @@
 		while (mpg123_ip.output->buffer_free() < mpg123_pcm_point && mpg123_info->going && mpg123_info->jump_to_time == -1)
 			xmms_usleep(10000);
 		if (mpg123_info->going && mpg123_info->jump_to_time == -1)
+		{
+			mpg123_voladjust(mpg123_pcm_sample, mpg123_pcm_point);
 			mpg123_ip.output->write_audio(mpg123_pcm_sample, mpg123_pcm_point);
+		}
 		
 
 	}
--- xmms-1.2.8-pre1/Input/mpg123/layer3.c.rva	2003-05-19 23:22:06.000000000 +0200
+++ xmms-1.2.8-pre1/Input/mpg123/layer3.c	2003-08-17 22:09:33.000000000 +0200
@@ -1947,7 +1947,10 @@
 			       mpg123_info->going && mpg123_info->jump_to_time == -1)
 				xmms_usleep(10000);
 			if (mpg123_info->going && mpg123_info->jump_to_time == -1)
+			{
+				mpg123_voladjust(mpg123_pcm_sample, mpg123_pcm_point);
 				mpg123_ip.output->write_audio(mpg123_pcm_sample, mpg123_pcm_point);
+			}
 		}
 		mpg123_pcm_point = 0;
 	}
--- xmms-1.2.8-pre1/Input/mpg123/Makefile.am.rva	2003-05-19 23:22:06.000000000 +0200
+++ xmms-1.2.8-pre1/Input/mpg123/Makefile.am	2003-08-17 22:09:33.000000000 +0200
@@ -6,11 +6,11 @@
 COMMON_SRC = mpg123.c configure.c fileinfo.c common.c \
 decode_2to1.c decode_4to1.c \
 layer1.c layer2.c layer3.c \
-tabinit.c equalizer.c http.c \
+tabinit.c equalizer.c http.c voladjust.c \
 huffman.h mpg123.h l2tables.h getbits.h \
 dxhead.c dxhead.h \
 id3.c id3.h id3_frame.c id3_frame_content.c id3_frame_text.c \
-id3_frame_url.c id3_header.h id3_tag.c
+id3_frame_url.c id3_frame_volume.c id3_header.h id3_tag.c
 
 if ARCH_X86
 
--- xmms-1.2.8-pre1/Input/mpg123/mpg123.c.rva	2003-08-08 19:10:41.000000000 +0200
+++ xmms-1.2.8-pre1/Input/mpg123/mpg123.c	2003-08-17 22:09:33.000000000 +0200
@@ -200,6 +200,10 @@
 	if (!xmms_cfg_read_string(cfg, "MPG123", "id3_format", &mpg123_cfg.id3_format))
 		mpg123_cfg.id3_format = g_strdup("%p - %t");
 	xmms_cfg_read_int(cfg, "MPG123", "detect_by", &mpg123_cfg.detect_by);
+ 	xmms_cfg_read_boolean(cfg, "MPG123", "use_rva2", &mpg123_cfg.use_rva2);
+ 	xmms_cfg_read_boolean(cfg, "MPG123", "use_boost", &mpg123_cfg.use_boost);
+ 	xmms_cfg_read_boolean(cfg, "MPG123", "enable_dither", &mpg123_cfg.enable_dither);
+ 	xmms_cfg_read_boolean(cfg, "MPG123", "anticlip_mode", &mpg123_cfg.anticlip_mode);
 	xmms_cfg_read_int(cfg, "MPG123", "default_synth", &mpg123_cfg.default_synth);
 
 	xmms_cfg_free(cfg);
@@ -910,6 +914,7 @@
 				mpg123_info->num_frames * mpg123_info->tpf * 1000;
 			if (!mpg123_title)
 				mpg123_title = get_song_title(NULL,filename);
+			mpg123_voladjust_update(filename);
 		}
 		else
 		{
@@ -1066,6 +1071,7 @@
 	g_free(mpg123_pcm_sample);
 	mpg123_filename = NULL;
 	g_free(filename);
+	mpg123_voladjust_cleanup();
 	pthread_exit(NULL);
 }
 
--- xmms-1.2.8-pre1/Input/mpg123/mpg123.h.rva	2003-05-19 23:23:09.000000000 +0200
+++ xmms-1.2.8-pre1/Input/mpg123/mpg123.h	2003-08-17 22:09:33.000000000 +0200
@@ -158,6 +158,8 @@
 	gboolean title_override, disable_id3v2;
 	int detect_by;
 	int default_synth;
+ 	gboolean use_rva2, use_boost, enable_dither;
+ 	gint anticlip_mode;
 }
 MPG123Config;
 
@@ -300,6 +302,11 @@
 gchar *mpg123_format_song_title(struct id3tag_t *tag, gchar *filename);
 double mpg123_relative_pos(void);
 
+extern float mpg123_current_gain;
+void mpg123_voladjust_cleanup(void);
+void mpg123_voladjust_update(char *filename);
+void mpg123_voladjust(void *ptr, int length);
+
 
 
 extern unsigned char *mpg123_conv16to8;
--- xmms-1.2.8-pre1/Input/mpg123/voladjust.c.rva	2003-08-17 22:09:33.000000000 +0200
+++ xmms-1.2.8-pre1/Input/mpg123/voladjust.c	2003-08-17 22:09:33.000000000 +0200
@@ -0,0 +1,356 @@
+#include "mpg123.h"
+#include <string.h>
+#include <stdlib.h>
+
+
+#define ROUND(x) ((int)floor((x) + 0.5))
+
+extern gchar *mpg123_filename;
+
+float mpg123_current_gain = 0.0;
+
+/* lookup table for fast gain adjustments */
+static gint32 *volume_lut = NULL;
+static struct id3_gain gain_info;
+static int adjusting = FALSE;
+
+/* track details of the lookup table, so we only rebuild if we need to */
+static struct {
+	AFormat lut_fmt;
+	float   lut_gain;
+	int     lut_limit;
+	int     lut_boost;
+} lut_data;
+
+/*
+ * A buffer of 8192 random numbers makes the period of the noise about
+ * 85 ms, or 11.7 Hz, for 48 kHz stereo.  This should be low enough to
+ * be unrecognizable except as noise.
+ */
+#define NOISEBUF_SZ 8192
+static char *noisebuf;
+static int noisebufi = 0;
+
+/*
+ * Returns 8 random bits, ie a number from -128 to +127.
+ * Used for dithering.
+ */
+static int
+rand8(void)
+{
+  int retval = (int)noisebuf[noisebufi];
+  noisebufi = (noisebufi + 1) % NOISEBUF_SZ;
+  return retval;
+}
+
+
+/*
+ * Limiter function:
+ *
+ *        / tanh((x + lev) / (1-lev)) * (1-lev) - lev        (for x < -lev)
+ *        |
+ *   x' = | x                                                (for |x| <= lev)
+ *        |
+ *        \ tanh((x - lev) / (1-lev)) * (1-lev) + lev        (for x > lev)
+ *
+ * With limiter level = 0, this is equivalent to a tanh() function;
+ * with limiter level = 1, this is equivalent to clipping.
+ */
+#define lmtr_lvl 0.5
+static double
+limiter(double x)
+{
+	double xp;
+
+	if (x < -lmtr_lvl)
+		xp = tanh((x + lmtr_lvl) / (1-lmtr_lvl)) * (1-lmtr_lvl) - lmtr_lvl;
+	else if (x <= lmtr_lvl)
+		xp = x;
+	else
+		xp = tanh((x - lmtr_lvl) / (1-lmtr_lvl)) * (1-lmtr_lvl) + lmtr_lvl;
+
+	return xp;
+}
+
+
+void mpg123_voladjust_cleanup(void)
+{
+	adjusting = FALSE;
+	if (noisebuf)
+		g_free(noisebuf);
+	noisebuf = NULL;
+	if (volume_lut)
+		g_free(volume_lut - 0x8000);
+	volume_lut = NULL;
+	return;
+}
+
+
+static void update_dither(void)
+{
+	int i;
+
+	if (mpg123_cfg.use_rva2 && mpg123_cfg.enable_dither)
+	{
+		/* fill the noise buffer, which we will cycle through
+		   when dithering */
+		if (noisebuf == NULL) {
+			noisebuf = g_malloc(NOISEBUF_SZ);
+			for (i = 0; i < (NOISEBUF_SZ / sizeof(int)); i++)
+				((int *)noisebuf)[i] = rand();
+			noisebufi = 0;
+			/* Note: noise shaping on the dithering noise
+			   is not implemented, but this would be the
+			   place to do it */
+		}
+	}
+	else
+	{
+		if (noisebuf)
+			g_free(noisebuf);
+		noisebuf = NULL;
+	}
+}
+
+
+static int gain_from_id3_tag(char *filename, struct id3_gain *gn)
+{
+	FILE *file;
+	id3_t *id3d;
+	id3_frame_t *id3frm;
+
+	file = fopen(filename, "rb");
+	if (file == NULL)
+		goto err_out;
+
+	id3d = id3_open_fp(file, 0);
+	if (id3d == NULL)
+		goto err_closefile;
+
+	id3frm = id3_get_frame(id3d, ID3_RVA2, 1);
+	if (id3frm == NULL)
+		goto err_closeid3;
+
+	if (id3_get_rva2_gain(id3frm, gn) == -1)
+		goto err_closeid3;
+
+	id3_close(id3d);
+	fclose(file);
+	return 1;
+
+ err_closeid3:
+	id3_close(id3d);
+ err_closefile:
+	fclose(file);
+ err_out:
+	memset(gn, 0, sizeof(struct id3_gain));
+	return 0;
+}
+
+
+void mpg123_voladjust_update(char *filename)
+{
+	struct id3_gain newgain;
+	float gain_db, gain_mult, scale;
+	int i, use_boost, use_limiter = TRUE, need_update = FALSE;
+	int samplemin, samplemax, center;
+	int lut_samplemin, lut_samplemax, lut_center;
+	AFormat fmt;
+	guint32 peak;
+
+	if (!mpg123_cfg.use_rva2)
+	{
+		mpg123_voladjust_cleanup();
+		return;
+	}
+
+	update_dither();
+
+	if (filename == NULL)
+		filename = mpg123_filename;
+
+	if (filename == NULL)
+		return;
+
+	if (!gain_from_id3_tag(filename, &newgain))
+	{
+		adjusting = FALSE;
+		mpg123_current_gain = 0.0;
+		memset(&gain_info, 0, sizeof(struct id3_gain));
+		return;
+	}
+
+	fmt = mpg123_cfg.resolution == 16 ? FMT_S16_NE : FMT_U8;
+	peak = newgain.channel[ID3_CHANNEL_MASTER].ch_peak;
+	gain_db = newgain.channel[ID3_CHANNEL_MASTER].ch_gain;
+
+	if (volume_lut == NULL)
+	{
+		/* the lookup table is 256k, but is deallocated if
+		   volume adjustment is disabled */
+		volume_lut = g_malloc(0x40000);
+		/* move the pointer to the center of the allocated
+		   region, so that we can use negative indices */
+		volume_lut += 0x8000;
+		need_update = TRUE;
+	}
+
+	gain_mult = pow(10, gain_db / 20.0);
+	use_boost = mpg123_cfg.use_boost;
+	if (use_boost)
+		gain_mult *= 2;
+
+	if (peak)
+	{
+		/*
+		 * Figure out if the peak, multiplied by the gain,
+		 * would clip.
+		 *
+		 * Some trickery here: we don't want to multiply our
+		 * 32-bit peak by the gain, since it may overflow.
+		 * Instead, we round it off to 24 bits. We then
+		 * compare with 0xFFFF80, instead of 0xFFFFFF, to
+		 * leave room for dithering.
+		 */
+		peak = ((peak >> 7) + 1) >> 1;
+		if (peak * gain_mult <= 0xFFFF80)
+			use_limiter = FALSE;
+
+		if (use_limiter && mpg123_cfg.anticlip_mode == 1)
+		{
+			/* Reduce gain until the peak won't clip. */
+			gain_mult = 0xFFFF80 / (float)peak;
+			use_limiter = FALSE;
+		}
+	}
+
+	if (!need_update
+	    && lut_data.lut_fmt == fmt
+	    && lut_data.lut_limit == use_limiter
+	    && lut_data.lut_boost == use_boost
+	    && fabs(lut_data.lut_gain - gain_db) < 0.0001)
+	{
+		/* the existing lookup table we have will work, no
+		   need to build a new one */
+		adjusting = TRUE;
+		return;
+	}
+
+	/* We store 8 extra bits of precision in the table, for use in
+	 * dithering. We can make this happen by simply multiplying
+	 * the gain by 256. */
+	gain_mult *= 256.0;
+
+	if (fmt == FMT_U8)
+	{
+		samplemax = 0xFF;
+		samplemin = 0;
+		lut_samplemax = 0xFFFF;
+		lut_samplemin = 0;
+	}
+	else
+	{ /* fmt == FMT_S16_NE */
+		samplemax = 0x7FFF;
+		samplemin = ~samplemax;
+		lut_samplemax = 0x7FFFFF;
+		lut_samplemin = ~lut_samplemax;
+	}
+
+	/* leave space for dither, which ranges from -128 to +127 */
+	lut_samplemax -= 127;
+	lut_samplemin += 128;
+
+	center = (samplemax + samplemin + 1) >> 1;
+	lut_center = (lut_samplemax + lut_samplemin + 1) >> 1;
+
+	/*
+	 * Build the lookup table
+	 */
+	if (use_limiter)
+	{
+		/* apply gain, and use limiter to avoid clipping */
+		scale = lut_center - lut_samplemin;
+		for (i = samplemin; i < 0; i++)
+			volume_lut[i] = lut_center + ROUND(scale * limiter((i-center) * gain_mult / scale));
+		scale = lut_samplemax - lut_center;
+		for (; i <= samplemax; i++)
+			volume_lut[i] = lut_center + ROUND(scale * limiter((i-center) * gain_mult / scale));
+	}
+	else
+	{
+		/* just apply gain if it wouldn't clip anyway */
+		for (i = samplemin; i <= samplemax; i++)
+			volume_lut[i] = lut_center + ROUND((i-center) * gain_mult);
+	}
+
+	lut_data.lut_fmt = fmt;
+	lut_data.lut_gain = gain_db;
+	lut_data.lut_limit = use_limiter;
+	lut_data.lut_boost = use_boost;
+	mpg123_current_gain = gain_db;
+	adjusting = TRUE;
+
+	return;
+}
+
+
+void mpg123_voladjust(void *ptr, int length)
+{
+	AFormat fmt;
+	int i;
+	long sample;
+	gint16 *ptr16 = NULL;
+	guint8 *ptru8 = NULL;
+
+	if (!adjusting || !mpg123_cfg.use_rva2 || volume_lut == NULL)
+		return;
+
+	fmt = mpg123_cfg.resolution == 16 ? FMT_S16_NE : FMT_U8;
+
+	if (fmt == FMT_U8)
+	{
+		ptru8 = (guint8 *)ptr;
+		if (mpg123_cfg.enable_dither)
+		{
+			for (i = 0; i < length; i++)
+			{
+				sample = volume_lut[*ptru8];
+				sample += rand8();
+				sample >>= 8;
+				*ptru8 = sample;
+				ptru8++;
+			}
+		}
+		else
+		{
+			for (i = 0; i < length; i++)
+			{
+				*ptru8 = volume_lut[*ptru8] >> 8;
+				ptru8++;
+			}
+		}
+	}
+	else
+	{ /* fmt == FMT_S16_NE */
+		ptr16 = (gint16 *)ptr;
+		if (mpg123_cfg.enable_dither)
+		{
+			for (i = 0; i < length; i += 2)
+			{
+				sample = volume_lut[*ptr16];
+				sample += rand8();
+				sample >>= 8;
+				*ptr16 = sample;
+				ptr16++;
+			}
+		}
+		else
+		{
+			for (i = 0; i < length; i += 2)
+			{
+				*ptr16 = volume_lut[*ptr16] >> 8;
+				ptr16++;
+			}
+		}
+	}
+}