From 04fdc209d9e2af8285c6e2015c2c5b69d0f85b5e Mon Sep 17 00:00:00 2001
From: Warren Dukes <warren.dukes@gmail.com>
Date: Fri, 7 May 2004 22:42:54 +0000
Subject: [PATCH] add replayGain stuff for flac from AliasMrJones

git-svn-id: https://svn.musicpd.org/mpd/trunk@943 09075e82-0dd4-0310-85a5-a0d7c8717e4f
---
 src/Makefile.am   |   4 +-
 src/conf.c        |   5 +-
 src/conf.h        |   1 +
 src/flac_decode.c | 205 ++++++++++++++++++++++++++--------------------
 src/flac_decode.h |   4 +-
 src/replayGain.c  |  80 ++++++++++++++++++
 src/replayGain.h  |  19 +++++
 7 files changed, 221 insertions(+), 97 deletions(-)
 create mode 100644 src/replayGain.c
 create mode 100644 src/replayGain.h

diff --git a/src/Makefile.am b/src/Makefile.am
index 33919516f..08924e63c 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -6,14 +6,14 @@ mpd_headers = buffer2array.h interface.h command.h playlist.h ls.h \
 	audio.h playerData.h stats.h myfprintf.h sig_handlers.h decode.h log.h \
 	audiofile_decode.h charConv.h permission.h mpd_types.h pcm_utils.h \
 	mp4_decode.h aac_decode.h signal_check.h utf8.h inputStream.h \
-	outputBuffer.h
+	outputBuffer.h replayGain.h
 mpd_SOURCES = main.c buffer2array.c interface.c command.c playlist.c ls.c \
 	song.c list.c directory.c tables.c utils.c path.c mp3_decode.c \
 	tag.c player.c listen.c conf.c ogg_decode.c volume.c flac_decode.c \
 	audio.c playerData.c stats.c myfprintf.c sig_handlers.c decode.c log.c \
 	audiofile_decode.c charConv.c permission.c pcm_utils.c mp4_decode.c \
 	aac_decode.c signal_check.c utf8.c inputStream.c outputBuffer.c \
-	$(mpd_headers)
+	replayGain.c $(mpd_headers)
 
 mpd_CFLAGS = $(MPD_CFLAGS)
 mpd_LDADD = $(MPD_LIBS) $(ID3_LIB) $(MAD_LIB) $(MP4FF_LIB)
diff --git a/src/conf.c b/src/conf.c
index 53287ea29..2027ae872 100644
--- a/src/conf.c
+++ b/src/conf.c
@@ -37,7 +37,7 @@
 
 #define CONF_COMMENT	'#'
 
-#define CONF_NUMBER_OF_PARAMS		27
+#define CONF_NUMBER_OF_PARAMS		28
 #define CONF_NUMBER_OF_PATHS		6
 #define CONF_NUMBER_OF_REQUIRED		5
 #define CONF_NUMBER_OF_ALLOW_CATS	1
@@ -123,7 +123,8 @@ char ** readConf(char * file) {
 		"filesystem_charset",
 		"password",
 		"default_permissions",
-		"buffer_size"
+		"buffer_size",
+                "replaygain"
 	};
 
 	int conf_absolutePaths[CONF_NUMBER_OF_PATHS] = {
diff --git a/src/conf.h b/src/conf.h
index d4d798b9c..3999f7f1f 100644
--- a/src/conf.h
+++ b/src/conf.h
@@ -48,6 +48,7 @@
 #define CONF_PASSWORD				24
 #define CONF_DEFAULT_PERMISSIONS		25
 #define CONF_BUFFER_SIZE			26
+#define CONF_REPLAYGAIN                         27
 
 #define CONF_CAT_CHAR				"\n"
 
diff --git a/src/flac_decode.c b/src/flac_decode.c
index b8d768428..7865d700a 100644
--- a/src/flac_decode.c
+++ b/src/flac_decode.c
@@ -25,6 +25,7 @@
 #include "pcm_utils.h"
 #include "inputStream.h"
 #include "outputBuffer.h"
+#include "replayGain.h"
 
 #include <stdio.h>
 #include <string.h>
@@ -41,8 +42,8 @@ typedef struct {
 	OutputBuffer * cb;
 	AudioFormat * af;
 	DecoderControl * dc;
-        char * file;
         InputStream inStream;
+        float replayGainScale;
 } FlacData;
 
 /* this code is based on flac123, from flac-tools */
@@ -66,9 +67,7 @@ FLAC__SeekableStreamDecoderLengthStatus flacLength(
                 const FLAC__SeekableStreamDecoder *, FLAC__uint64 *, void *);
 FLAC__bool flacEOF(const FLAC__SeekableStreamDecoder *, void *);
 
-void flacPlayFile(char *file, OutputBuffer * cb, AudioFormat * af, 
-	DecoderControl *dc)
-{
+int flac_decode(OutputBuffer * cb, AudioFormat * af, DecoderControl *dc) {
 	FLAC__SeekableStreamDecoder * flacDec;
 	FlacData data;
 	int status = 1;
@@ -80,14 +79,14 @@ void flacPlayFile(char *file, OutputBuffer * cb, AudioFormat * af,
 	data.cb = cb;
 	data.af = af;
 	data.dc = dc;
-        data.file = file;
+        data.replayGainScale = 1.0;
 
-        if(openInputStreamFromFile(&(data.inStream),file)<0) {
-                ERROR("unable to open flac: %s\n",file);
-                return;
+        if(openInputStreamFromFile(&(data.inStream),dc->file)<0) {
+                ERROR("unable to open flac: %s\n",dc->file);
+                return -1;
         }
 	
-	if(!(flacDec = FLAC__seekable_stream_decoder_new())) return;
+	if(!(flacDec = FLAC__seekable_stream_decoder_new())) return -1;
 	/*status&=FLAC__file_decoder_set_md5_checking(flacDec,1);*/
 	status&=FLAC__seekable_stream_decoder_set_read_callback(flacDec,
                         flacRead);
@@ -107,28 +106,37 @@ void flacPlayFile(char *file, OutputBuffer * cb, AudioFormat * af,
 	status&=FLAC__seekable_stream_decoder_set_client_data(flacDec,
                         (void *)&data);
 	if(!status) {
-		ERROR("flac problem before init(): %s\n",file);
+		ERROR("flac problem before init(): %s\n",dc->file);
 		flacPrintErroredState(
-                        FLAC__seekable_stream_decoder_get_state(flacDec),file);
+                        FLAC__seekable_stream_decoder_get_state(flacDec),
+                                        dc->file);
 		FLAC__seekable_stream_decoder_delete(flacDec);
-		return;
+		return -1;
 	}
+
 	if(FLAC__seekable_stream_decoder_init(flacDec)!=
 			FLAC__STREAM_DECODER_SEARCH_FOR_METADATA) 
 	{
-		ERROR("flac problem doing init(): %s\n",file);
+		ERROR("flac problem doing init(): %s\n",dc->file);
 		flacPrintErroredState(
-                        FLAC__seekable_stream_decoder_get_state(flacDec),file);
+                        FLAC__seekable_stream_decoder_get_state(flacDec),
+                                        dc->file);
 		FLAC__seekable_stream_decoder_delete(flacDec);
-		return;
+		return -1;
 	}
+
 	if(!FLAC__seekable_stream_decoder_process_until_end_of_metadata(flacDec)) {
-		ERROR("flac problem reading metadata: %s\n",file);
+		ERROR("flac problem reading metadata: %s\n", dc->file);
 		flacPrintErroredState(
-                        FLAC__seekable_stream_decoder_get_state(flacDec),file);
+                        FLAC__seekable_stream_decoder_get_state(flacDec), 
+                                        dc->file);
 		FLAC__seekable_stream_decoder_delete(flacDec);
-		return;
+		return -1;
 	}
+
+	dc->state = DECODE_STATE_DECODE;
+	dc->start = 0;
+
 	while(1) {
 		FLAC__seekable_stream_decoder_process_single(flacDec);
 		if(FLAC__seekable_stream_decoder_get_state(flacDec)!=
@@ -155,7 +163,8 @@ void flacPlayFile(char *file, OutputBuffer * cb, AudioFormat * af,
 	/*FLAC__file_decoder_process_until_end_of_file(flacDec);*/
 	if(!dc->stop) {
 		flacPrintErroredState(
-                        FLAC__seekable_stream_decoder_get_state(flacDec),file);
+                        FLAC__seekable_stream_decoder_get_state(flacDec), 
+                                        dc->file);
 		FLAC__seekable_stream_decoder_finish(flacDec);
 	}
 	FLAC__seekable_stream_decoder_delete(flacDec);
@@ -164,6 +173,16 @@ void flacPlayFile(char *file, OutputBuffer * cb, AudioFormat * af,
 		flacSendChunk(&data);
 		flushOutputBuffer(data.cb);
 	}
+
+	if(dc->seek) dc->seek = 0;
+	
+	if(dc->stop) {
+		dc->state = DECODE_STATE_STOP;
+		dc->stop = 0;
+	}
+	else dc->state = DECODE_STATE_STOP;
+
+	return 0;
 }
 
 FLAC__SeekableStreamDecoderReadStatus flacRead(
@@ -232,16 +251,16 @@ void flacError(const FLAC__SeekableStreamDecoder *dec,
 
 	switch(status) {
 	case FLAC__STREAM_DECODER_ERROR_STATUS_LOST_SYNC:
-		ERROR("flac lost sync: %s\n",data->file);
+		ERROR("flac lost sync: %s\n",data->dc->file);
 		break;
 	case FLAC__STREAM_DECODER_ERROR_STATUS_BAD_HEADER:
-		ERROR("bad header %s\n",data->file);
+		ERROR("bad header %s\n",data->dc->file);
 		break;
 	case FLAC__STREAM_DECODER_ERROR_STATUS_FRAME_CRC_MISMATCH:
-		ERROR("crc mismatch %s\n",data->file);
+		ERROR("crc mismatch %s\n",data->dc->file);
 		break;
 	default:
-		ERROR("unknow flac error %s\n",data->file);
+		ERROR("unknow flac error %s\n",data->dc->file);
 	}
 }
 
@@ -277,12 +296,83 @@ void flacPrintErroredState(FLAC__SeekableStreamDecoderState state,
 	}
 }
 
+int flacFindVorbisCommentFloat(const FLAC__StreamMetadata * block, char * cmnt, 
+                float * fl)
+{
+        int offset = FLAC__metadata_object_vorbiscomment_find_entry_from(
+                        block,0,cmnt);
+
+        if(offset >= 0) {
+                int pos = strlen(cmnt)+1; /* 1 is for '=' */
+                int len = block->data.vorbis_comment.comments[offset].length
+                                -pos;
+                if(len > 0) {
+                        char * dup = malloc(len+1);
+                        memcpy(dup,&(block->data.vorbis_comment.comments[offset].entry[pos]),len);
+                        dup[len] = '\0';
+                        *fl = atof(dup);
+                        free(dup);
+                        return 1;
+                }
+        }
+
+        return 0;
+}
+
+/* replaygain stuff by AliasMrJones */
+void flacParseReplayGain(const FLAC__StreamMetadata *block, FlacData * data) {
+        int found;
+        float gain = 0.0;
+        float peak = 0.0;
+        int state = getReplayGainState();
+
+        if(state == REPLAYGAIN_OFF) return;
+
+        found = flacFindVorbisCommentFloat(block,"replaygain_album_gain",&gain);
+        if(found) {
+                flacFindVorbisCommentFloat(block,"replaygain_album_peak",
+                                &peak);
+        }
+
+        if(!found || state == REPLAYGAIN_TRACK) {
+                if(flacFindVorbisCommentFloat(block,"replaygain_track_gain",
+                                &gain))
+                {
+                        peak = 0.0;
+                        flacFindVorbisCommentFloat(block,
+                                        "replaygain_track_peak",&peak);
+                }
+        }
+
+        data->replayGainScale = computeReplayGainScale(gain,peak);
+}
+
 void flacMetadata(const FLAC__SeekableStreamDecoder *dec, 
-                const FLAC__StreamMetadata *meta, void *data) 
+                const FLAC__StreamMetadata *block, void *vdata) 
 {
+	FlacData * data = (FlacData *)vdata;
+
+        switch(block->type) {
+        case FLAC__METADATA_TYPE_STREAMINFO:
+		data->af->bits = block->data.stream_info.bits_per_sample;
+		data->af->bits = 16;
+		data->af->sampleRate = block->data.stream_info.sample_rate;
+		data->af->channels = block->data.stream_info.channels;
+		data->cb->totalTime = 
+                                ((float)block->data.stream_info.total_samples)/
+			        data->af->sampleRate;
+                break;
+        case FLAC__METADATA_TYPE_VORBIS_COMMENT:
+                flacParseReplayGain(block,data);
+        default:
+                break;
+        }
 }
 
 int flacSendChunk(FlacData * data) {
+        doReplayGain(data->chunk,data->chunk_length,data->af,
+                        data->replayGainScale);
+
 	switch(sendDataToOutputBuffer(data->cb,data->dc,data->chunk,
 			data->chunk_length,data->time,data->bitRate)) 
 	{
@@ -341,70 +431,5 @@ FLAC__StreamDecoderWriteStatus flacWrite(const FLAC__SeekableStreamDecoder *dec,
 	return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
 }
 
-int flac_getAudioFormatAndTime(char * file, AudioFormat * format, float * time) {
-	FLAC__Metadata_SimpleIterator * it;
-	FLAC__StreamMetadata * block = NULL;
-	int found = 0;
-	int ret = -1;
-
-	if(!(it = FLAC__metadata_simple_iterator_new())) return -1;
-        if(!FLAC__metadata_simple_iterator_init(it,file,1,0)) {
-                FLAC__metadata_simple_iterator_delete(it);
-	        return -1;
-	}
-
-        do {
-                if(block) FLAC__metadata_object_delete(block);
-                block = FLAC__metadata_simple_iterator_get_block(it);
-                if(block->type == FLAC__METADATA_TYPE_STREAMINFO) found=1;
-        } while(!found && FLAC__metadata_simple_iterator_next(it));
-
-	if(found) {
-		format->bits = block->data.stream_info.bits_per_sample;
-		format->bits = 16;
-		format->sampleRate = block->data.stream_info.sample_rate;
-		format->channels = block->data.stream_info.channels;
-		*time = ((float)block->data.stream_info.total_samples)/
-			format->sampleRate;
-		ret = 0;
-	}
-
-	if(block) FLAC__metadata_object_delete(block);
-	FLAC__metadata_simple_iterator_delete(it);
-
-	return ret;
-}
-
-int getFlacTotalTime(char * file) {
-	float totalTime;
-	AudioFormat af;
-
-	if(flac_getAudioFormatAndTime(file,&af,&totalTime)<0) return -1;
-
-	return (int)(totalTime+0.5);
-}
-
-int flac_decode(OutputBuffer * cb, AudioFormat * af, DecoderControl * dc) {
-	if(flac_getAudioFormatAndTime(dc->file,af,&(cb->totalTime))<0) {
-		ERROR("\"%s\" doesn't seem to be a flac\n",dc->file);
-		return -1;
-	}
-
-	dc->state = DECODE_STATE_DECODE;
-	dc->start = 0;
-
-	flacPlayFile(dc->file,cb,af,dc);
-
-	if(dc->seek) dc->seek = 0;
-	
-	if(dc->stop) {
-		dc->state = DECODE_STATE_STOP;
-		dc->stop = 0;
-	}
-	else dc->state = DECODE_STATE_STOP;
-
-	return 0;
-}
-
 #endif
-/* vim:set shiftwidth=4 tabstop=8 expandtab: */
+/* vim:set shiftwidth=8 tabstop=8 expandtab: */
diff --git a/src/flac_decode.h b/src/flac_decode.h
index db7662334..4858274f8 100644
--- a/src/flac_decode.h
+++ b/src/flac_decode.h
@@ -27,7 +27,5 @@
 
 int flac_decode(OutputBuffer * cb, AudioFormat * af, DecoderControl * dc);
 
-int getFlacTotalTime(char * file);
-
 #endif
-/* vim:set shiftwidth=4 tabstop=8 expandtab: */
+/* vim:set shiftwidth=8 tabstop=8 expandtab: */
diff --git a/src/replayGain.c b/src/replayGain.c
new file mode 100644
index 000000000..ece583a06
--- /dev/null
+++ b/src/replayGain.c
@@ -0,0 +1,80 @@
+
+#include "replayGain.h"
+
+#include "log.h"
+#include "conf.h"
+
+#include <string.h>
+#include <math.h>
+#include <stdlib.h>
+ 
+/* Added 4/14/2004 by AliasMrJones */
+static int replayGainState = REPLAYGAIN_OFF;
+
+void initReplayGainState() {
+	if(!getConf()[CONF_REPLAYGAIN]) return;
+
+	if(strcmp(getConf()[CONF_REPLAYGAIN],"track")==0) {
+		replayGainState = REPLAYGAIN_TRACK;
+	}
+	else if(strcmp(getConf()[CONF_REPLAYGAIN],"album")==0) {
+		replayGainState = REPLAYGAIN_ALBUM;
+	}
+	else {
+		ERROR("replaygain value \"%s\" is invalid\n",
+				getConf()[CONF_REPLAYGAIN]);
+		exit(EXIT_FAILURE);
+	}
+}
+
+int getReplayGainState() {
+	return replayGainState;
+}
+
+float computeReplayGainScale(float gain, float peak){
+	float scale;
+
+	if(gain == 0.0) return(1);
+	scale = pow(10.0, gain/20.0);
+	if(scale > 15.0) scale = 15.0;
+
+	if (scale * peak > 1.0) {
+		scale = 1.0 / peak;
+	}
+	return(scale);
+}
+
+void doReplayGain(char * buffer, int bufferSize, AudioFormat * format, 
+		float scale) 
+{
+	mpd_sint16 * buffer16 = (mpd_sint16 *)buffer;
+	mpd_sint8 * buffer8 = (mpd_sint8 *)buffer;
+	mpd_sint32 temp32;
+
+	if(scale == 1.0) return;
+	switch(format->bits) {
+		case 16:
+			while(bufferSize > 0){
+				temp32 = *buffer16;
+				temp32 *= scale;
+				*buffer16 = temp32>32767 ? 32767 : 
+					(temp32<-32768 ? -32768 : temp32);
+				buffer16++;
+				bufferSize-=2;
+			}
+			break;
+		case 8:
+			while(bufferSize>0){
+				temp32 = *buffer8;
+				temp32 *= scale;
+				*buffer8 = temp32>127 ? 127 : 
+					(temp32<-128 ? -128 : temp32);
+				buffer8++;
+				bufferSize--;
+			}
+			break;
+		default:
+			ERROR("%i bits not supported by doReplaygain!\n", format->bits);
+	}
+}
+/* End of added code */
diff --git a/src/replayGain.h b/src/replayGain.h
new file mode 100644
index 000000000..6ab3897c7
--- /dev/null
+++ b/src/replayGain.h
@@ -0,0 +1,19 @@
+#ifndef REPLAYGAIN_H
+#define REPLAYGAIN_H
+
+#include "audio.h"
+
+#define REPLAYGAIN_OFF		0
+#define REPLAYGAIN_TRACK	1
+#define REPLAYGAIN_ALBUM	2
+
+void initReplayGainState();
+
+int getReplayGainState();
+
+float computeReplayGainScale(float gain, float peak);
+
+void doReplayGain(char * buffer, int bufferSize, AudioFormat * format, 
+		float scale);
+
+#endif
-- 
2.24.1