Mercurial > audlegacy-plugins
diff src/madplug/replaygain.c @ 610:862190d39e00 trunk
[svn] - add madplug. It is not yet hooked up, I'll do that later.
author | nenolod |
---|---|
date | Mon, 05 Feb 2007 12:28:01 -0800 |
parents | |
children | 3f7a52adfe0e |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/madplug/replaygain.c Mon Feb 05 12:28:01 2007 -0800 @@ -0,0 +1,254 @@ +/* + * mad plugin for audacious + * Copyright (C) 2005-2007 William Pitcock, Yoshiki Yazawa + * + * Portions derived from xmms-mad: + * Copyright (C) 2001-2002 Sam Clegg - See COPYING + * Copyright (C) 2001-2007 Samuel Krempp + * + * 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; under version 2 of the License. + * + * 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 "plugin.h" +#include <stdlib.h> +#include <math.h> +#include <ctype.h> +#include <assert.h> +#include "replaygain.h" + +static unsigned long Read_LE_Uint32(const unsigned char *p) +{ + return ((unsigned long) p[0] << 0) | + ((unsigned long) p[1] << 8) | + ((unsigned long) p[2] << 16) | ((unsigned long) p[3] << 24); +} + +static int uncase_strcmp(const char *s1, const char *s2) +{ + int l1 = strlen(s1); + int l2 = strlen(s2); + int i; + for (i = 0; i < l1 && i < l2; ++i) { + if (toupper(s1[i]) < toupper(s2[i])) + return -1; + } + if (l1 == l2) + return 0; + return (l1 < l2) ? -1 : +1; +} + +static gdouble strgain2double(gchar * s, int len) +{ + gdouble res = g_strtod(s, NULL); // gain, in dB. + if (res == 0) + return 1; + return pow(10, res / 20); +} + +// Reads APE v2.0 tag ending at current pos in fp + +static int ReadAPE2Tag(VFSFile * fp, struct mad_info_t *file) +{ + unsigned long vsize; + unsigned long isize; + unsigned long flags; + char *buff; + char *p; + char *end; + struct APETagFooterStruct T, *tp; + unsigned long TagLen; + unsigned long TagCount; + + tp = &T; + + if (vfs_fseek(fp, -sizeof(T), SEEK_CUR) != 0) + return 18; + if (vfs_fread(tp, 1, sizeof(T), fp) != sizeof T) + return 2; + if (memcmp(tp->ID, "APETAGEX", sizeof(tp->ID)) != 0) + return 3; + if (Read_LE_Uint32(tp->Version) != 2000) + return 4; + TagLen = Read_LE_Uint32(tp->Length); + if (TagLen < sizeof(T)) + return 5; + if (vfs_fseek(fp, -TagLen, SEEK_CUR) != 0) + return 6; + if ((buff = (char *) malloc(TagLen)) == NULL) { + return 7; + } + if (vfs_fread(buff, 1, TagLen - sizeof(T), fp) != TagLen - sizeof(T)) { + free(buff); + return 8; + } +#ifdef DEBUG + printf("ver = %ld\n", Read_LE_Uint32(tp->Version)); + printf("taglen = %ld\n", TagLen); +#endif + + TagCount = Read_LE_Uint32(tp->TagCount); + end = buff + TagLen - sizeof(T); + for (p = buff; p < end && TagCount--;) { + vsize = Read_LE_Uint32((unsigned char *)p); + p += 4; + flags = Read_LE_Uint32((unsigned char *)p); + p += 4; + isize = strlen((char *) p); + + if (isize > 0 && vsize > 0) { + gdouble *scale = NULL; + gchar **str = NULL; + if (uncase_strcmp(p, "REPLAYGAIN_ALBUM_GAIN") == 0) { + scale = &file->replaygain_album_scale; + str = &file->replaygain_album_str; + } + if (uncase_strcmp(p, "REPLAYGAIN_TRACK_GAIN") == 0) { + scale = &file->replaygain_track_scale; + str = &file->replaygain_track_str; + } + if (str != NULL) { + assert(scale != NULL); + *scale = strgain2double(p + isize + 1, vsize); + *str = g_strndup(p + isize + 1, vsize); + } + //* case of peak info tags : */ + str = NULL; + if (uncase_strcmp(p, "REPLAYGAIN_TRACK_PEAK") == 0) { + scale = &file->replaygain_track_peak; + str = &file->replaygain_track_peak_str; + } + if (uncase_strcmp(p, "REPLAYGAIN_ALBUM_PEAK") == 0) { + scale = &file->replaygain_album_peak; + str = &file->replaygain_album_peak_str; + } + if (str != NULL) { + *scale = g_strtod(p + isize + 1, NULL); + *str = g_strndup(p + isize + 1, vsize); + } + + /* mp3gain additional tags : + the gain tag translates to scale = 2^(gain/4), + i.e., in dB : 20*log(2)/log(10)*gain/4 + -> 1.501*gain dB + */ + if (uncase_strcmp(p, "MP3GAIN_UNDO") == 0) { + str = &file->mp3gain_undo_str; + scale = &file->mp3gain_undo; + assert(4 < vsize); /* this tag is +left,+right */ + *str = g_strndup(p + isize + 1, vsize); + *scale = 1.50515 * atoi(*str); + } + if (uncase_strcmp(p, "MP3GAIN_MINMAX") == 0) { + str = &file->mp3gain_minmax_str; + scale = &file->mp3gain_minmax; + *str = g_strndup(p + isize + 1, vsize); + assert(4 < vsize); /* this tag is min,max */ + *scale = 1.50515 * (atoi((*str) + 4) - atoi(*str)); + } + } + p += isize + 1 + vsize; + } + + free(buff); + + return 0; +} + +static int find_offset(VFSFile * fp) +{ + static const char *key = "APETAGEX"; + char buff[20000]; + int N = 0; + if (vfs_fseek(fp, -20000, SEEK_CUR) != 0); + if ((N = vfs_fread(buff, 1, 20000, fp)) < 16) + return 1; + int matched = 0; + int i, last_match = -1; + for (i = 0; i < N; ++i) { + if (buff[i] == key[matched]) + ++matched; + else { + if (matched == 5 && buff[i] == 'P') + matched = 2; // got "APET" + "AP" + else + matched = 0; + } + if (matched == 8) { + last_match = i; + matched = 0; + } + } + if (last_match == -1) + return 1; + return last_match + 1 - 8 + sizeof(struct APETagFooterStruct) - N; +} + +void input_read_replaygain(struct mad_info_t *file) +{ + file->has_replaygain = FALSE; + file->replaygain_album_scale = -1; + file->replaygain_track_scale = -1; + file->mp3gain_undo = -77; + file->mp3gain_minmax = -77; + + + VFSFile *fp; + + if ((fp = vfs_fopen(file->filename, "rb")) == NULL) + return; + + if (vfs_fseek(fp, 0L, SEEK_END) != 0) { + vfs_fclose(fp); + return; + } + long pos = vfs_ftell(fp); + int res = -1; + int try = 0; + while (res != 0 && try < 10) { + // try skipping an id3 tag + vfs_fseek(fp, pos, SEEK_SET); + vfs_fseek(fp, try * -128, SEEK_CUR); + res = ReadAPE2Tag(fp, file); + ++try; + } + if (res != 0) { + // try brute search (don't want to parse all possible kinds of tags..) + vfs_fseek(fp, pos, SEEK_SET); + int offs = find_offset(fp); + if (offs <= 0) { // found ! + vfs_fseek(fp, pos, SEEK_SET); + vfs_fseek(fp, offs, SEEK_CUR); + res = ReadAPE2Tag(fp, file); + if (res != 0) { + g_message + ("hmpf, was supposed to find a tag.. offs=%d, res=%d", + offs, res); + } + } + } +#ifdef DEBUG +//#if 1 + if (res == 0) { // got APE tags, show the result + printf("RG album scale= %g, RG track scale = %g, in %s \n", + file->replaygain_album_scale, + file->replaygain_track_scale, file->filename); + } +#endif + + if (file->replaygain_album_scale != -1 + || file->replaygain_track_scale != -1) + file->has_replaygain = TRUE; + + vfs_fclose(fp); +}