2005-07-24 15:25:13 +00:00
|
|
|
/***************************************************************************
|
|
|
|
* __________ __ ___.
|
|
|
|
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
|
|
|
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
|
|
|
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
|
|
|
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
|
|
|
* \/ \/ \/ \/ \/
|
|
|
|
* $Id$
|
|
|
|
*
|
|
|
|
* Copyright (C) 2005 Magnus Holmgren
|
|
|
|
*
|
2008-06-28 18:10:04 +00:00
|
|
|
* 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.
|
2005-07-24 15:25:13 +00:00
|
|
|
*
|
|
|
|
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
|
|
|
* KIND, either express or implied.
|
|
|
|
*
|
|
|
|
****************************************************************************/
|
|
|
|
|
|
|
|
#include <ctype.h>
|
|
|
|
#include <inttypes.h>
|
|
|
|
#include <math.h>
|
2005-07-27 11:54:33 +00:00
|
|
|
#include <stdbool.h>
|
2005-07-24 15:25:13 +00:00
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
2005-07-27 11:54:33 +00:00
|
|
|
#include <string.h>
|
|
|
|
#include <system.h>
|
2008-10-15 06:38:51 +00:00
|
|
|
#include "metadata.h"
|
2005-07-24 15:25:13 +00:00
|
|
|
#include "debug.h"
|
2008-05-03 08:35:14 +00:00
|
|
|
#include "replaygain.h"
|
2009-07-05 18:06:07 +00:00
|
|
|
#include "fixedpoint.h"
|
2005-07-24 15:25:13 +00:00
|
|
|
|
|
|
|
#define FP_BITS (12)
|
|
|
|
#define FP_ONE (1 << FP_BITS)
|
|
|
|
|
|
|
|
|
|
|
|
static long fp_atof(const char* s, int precision)
|
|
|
|
{
|
|
|
|
long int_part = 0;
|
2009-06-07 21:27:05 +00:00
|
|
|
long int_one = BIT_N(precision);
|
2005-07-24 15:25:13 +00:00
|
|
|
long frac_part = 0;
|
|
|
|
long frac_count = 0;
|
|
|
|
long frac_max = ((precision * 4) + 12) / 13;
|
|
|
|
long frac_max_int = 1;
|
|
|
|
long sign = 1;
|
|
|
|
bool point = false;
|
|
|
|
|
2009-02-08 11:28:54 +00:00
|
|
|
while ((*s != '\0') && isspace(*s))
|
2010-02-07 18:38:47 +00:00
|
|
|
{
|
|
|
|
s++;
|
2009-02-08 11:28:54 +00:00
|
|
|
}
|
2005-07-24 15:25:13 +00:00
|
|
|
|
|
|
|
if (*s == '-')
|
|
|
|
{
|
|
|
|
sign = -1;
|
|
|
|
s++;
|
|
|
|
}
|
|
|
|
else if (*s == '+')
|
|
|
|
{
|
|
|
|
s++;
|
|
|
|
}
|
|
|
|
|
|
|
|
while (*s != '\0')
|
|
|
|
{
|
|
|
|
if (*s == '.')
|
|
|
|
{
|
|
|
|
if (point)
|
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
point = true;
|
|
|
|
}
|
|
|
|
else if (isdigit(*s))
|
|
|
|
{
|
|
|
|
if (point)
|
|
|
|
{
|
|
|
|
if (frac_count < frac_max)
|
|
|
|
{
|
|
|
|
frac_part = frac_part * 10 + (*s - '0');
|
|
|
|
frac_count++;
|
|
|
|
frac_max_int *= 10;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
int_part = int_part * 10 + (*s - '0');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
s++;
|
|
|
|
}
|
|
|
|
|
|
|
|
while (frac_count < frac_max)
|
|
|
|
{
|
|
|
|
frac_part *= 10;
|
|
|
|
frac_count++;
|
|
|
|
frac_max_int *= 10;
|
|
|
|
}
|
|
|
|
|
2006-07-20 10:35:20 +00:00
|
|
|
return sign * ((int_part * int_one)
|
2005-07-24 15:25:13 +00:00
|
|
|
+ (((int64_t) frac_part * int_one) / frac_max_int));
|
|
|
|
}
|
|
|
|
|
|
|
|
static long convert_gain(long gain)
|
|
|
|
{
|
2007-02-23 12:22:17 +00:00
|
|
|
/* Don't allow unreasonably low or high gain changes.
|
|
|
|
* Our math code can't handle it properly anyway. :)
|
|
|
|
*/
|
|
|
|
if (gain < (-48 * FP_ONE))
|
2005-07-24 15:25:13 +00:00
|
|
|
{
|
2007-02-23 12:22:17 +00:00
|
|
|
gain = -48 * FP_ONE;
|
|
|
|
}
|
2005-07-24 15:25:13 +00:00
|
|
|
|
2007-02-23 12:22:17 +00:00
|
|
|
if (gain > (17 * FP_ONE))
|
|
|
|
{
|
|
|
|
gain = 17 * FP_ONE;
|
2005-07-24 15:25:13 +00:00
|
|
|
}
|
2006-07-20 10:35:20 +00:00
|
|
|
|
2009-07-05 18:06:07 +00:00
|
|
|
gain = fp_factor(gain, FP_BITS) << (24 - FP_BITS);
|
2007-02-23 12:22:17 +00:00
|
|
|
|
2005-07-24 15:25:13 +00:00
|
|
|
return gain;
|
|
|
|
}
|
|
|
|
|
2007-08-25 10:25:13 +00:00
|
|
|
/* Get the sample scale factor in Q7.24 format from a gain value. Returns 0
|
|
|
|
* for no gain.
|
|
|
|
*
|
|
|
|
* str Gain in dB as a string. E.g., "-3.45 dB"; the "dB" part is ignored.
|
|
|
|
*/
|
|
|
|
static long get_replaygain(const char* str)
|
2005-07-24 15:25:13 +00:00
|
|
|
{
|
|
|
|
long gain = 0;
|
2006-07-20 10:35:20 +00:00
|
|
|
|
2005-07-24 15:25:13 +00:00
|
|
|
if (str)
|
|
|
|
{
|
|
|
|
gain = fp_atof(str, FP_BITS);
|
|
|
|
gain = convert_gain(gain);
|
|
|
|
}
|
2006-07-20 10:35:20 +00:00
|
|
|
|
2005-07-24 15:25:13 +00:00
|
|
|
return gain;
|
|
|
|
}
|
|
|
|
|
2007-08-25 10:25:13 +00:00
|
|
|
/* Get the peak volume in Q7.24 format.
|
|
|
|
*
|
|
|
|
* str Peak volume. Full scale is specified as "1.0". Returns 0 for no peak.
|
|
|
|
*/
|
|
|
|
static long get_replaypeak(const char* str)
|
2005-07-24 15:25:13 +00:00
|
|
|
{
|
|
|
|
long peak = 0;
|
2006-07-20 10:35:20 +00:00
|
|
|
|
2005-07-24 15:25:13 +00:00
|
|
|
if (str)
|
|
|
|
{
|
|
|
|
peak = fp_atof(str, 24);
|
|
|
|
}
|
2006-07-20 10:35:20 +00:00
|
|
|
|
2005-07-24 15:25:13 +00:00
|
|
|
return peak;
|
|
|
|
}
|
2005-07-27 11:54:33 +00:00
|
|
|
|
2007-08-25 10:25:13 +00:00
|
|
|
/* Get a sample scale factor in Q7.24 format from a gain value.
|
|
|
|
*
|
|
|
|
* int_gain Gain in dB, multiplied by 100.
|
|
|
|
*/
|
|
|
|
long get_replaygain_int(long int_gain)
|
|
|
|
{
|
|
|
|
return convert_gain(int_gain * FP_ONE / 100);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Parse a ReplayGain tag conforming to the "VorbisGain standard". If a
|
|
|
|
* valid tag is found, update mp3entry struct accordingly. Existing values
|
|
|
|
* are not overwritten. Returns number of bytes written to buffer.
|
|
|
|
*
|
|
|
|
* key Name of the tag.
|
|
|
|
* value Value of the tag.
|
|
|
|
* entry mp3entry struct to update.
|
|
|
|
* buffer Where to store the text for gain values (for later display).
|
|
|
|
* length Bytes left in buffer.
|
2005-07-27 11:54:33 +00:00
|
|
|
*/
|
2006-07-20 10:35:20 +00:00
|
|
|
long parse_replaygain(const char* key, const char* value,
|
2005-07-27 11:54:33 +00:00
|
|
|
struct mp3entry* entry, char* buffer, int length)
|
|
|
|
{
|
|
|
|
char **p = NULL;
|
|
|
|
|
2006-07-21 20:15:01 +00:00
|
|
|
if (((strcasecmp(key, "replaygain_track_gain") == 0)
|
|
|
|
|| (strcasecmp(key, "rg_radio") == 0)) && !entry->track_gain)
|
2005-07-27 11:54:33 +00:00
|
|
|
{
|
2005-10-03 18:35:02 +00:00
|
|
|
entry->track_gain = get_replaygain(value);
|
2005-07-27 11:54:33 +00:00
|
|
|
p = &(entry->track_gain_string);
|
2006-07-20 10:35:20 +00:00
|
|
|
}
|
2006-07-21 20:15:01 +00:00
|
|
|
else if (((strcasecmp(key, "replaygain_album_gain") == 0)
|
|
|
|
|| (strcasecmp(key, "rg_audiophile") == 0)) && !entry->album_gain)
|
2005-07-27 11:54:33 +00:00
|
|
|
{
|
2005-10-03 18:35:02 +00:00
|
|
|
entry->album_gain = get_replaygain(value);
|
2005-07-27 11:54:33 +00:00
|
|
|
p = &(entry->album_gain_string);
|
|
|
|
}
|
2006-07-21 20:15:01 +00:00
|
|
|
else if (((strcasecmp(key, "replaygain_track_peak") == 0)
|
|
|
|
|| (strcasecmp(key, "rg_peak") == 0)) && !entry->track_peak)
|
2005-07-27 11:54:33 +00:00
|
|
|
{
|
2005-10-03 18:35:02 +00:00
|
|
|
entry->track_peak = get_replaypeak(value);
|
2006-07-20 10:35:20 +00:00
|
|
|
}
|
2006-07-21 20:15:01 +00:00
|
|
|
else if ((strcasecmp(key, "replaygain_album_peak") == 0)
|
|
|
|
&& !entry->album_peak)
|
2005-07-27 11:54:33 +00:00
|
|
|
{
|
2005-10-03 18:35:02 +00:00
|
|
|
entry->album_peak = get_replaypeak(value);
|
2005-07-27 11:54:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (p)
|
|
|
|
{
|
2005-10-03 18:35:02 +00:00
|
|
|
int len = strlen(value);
|
2006-07-20 10:35:20 +00:00
|
|
|
|
2005-07-27 11:54:33 +00:00
|
|
|
len = MIN(len, length - 1);
|
|
|
|
|
|
|
|
/* A few characters just isn't interesting... */
|
|
|
|
if (len > 1)
|
|
|
|
{
|
2009-07-14 13:57:45 +00:00
|
|
|
strlcpy(buffer, value, len + 1);
|
2005-07-27 11:54:33 +00:00
|
|
|
*p = buffer;
|
|
|
|
return len + 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
2006-07-20 10:35:20 +00:00
|
|
|
|
2007-08-25 10:25:13 +00:00
|
|
|
/* Set ReplayGain values from integers. Existing values are not overwritten.
|
|
|
|
* Returns number of bytes written to buffer.
|
|
|
|
*
|
|
|
|
* album If true, set album values, otherwise set track values.
|
|
|
|
* gain Gain value in dB, multiplied by 512. 0 for no gain.
|
|
|
|
* peak Peak volume in Q7.24 format, where 1.0 is full scale. 0 for no
|
|
|
|
* peak volume.
|
|
|
|
* buffer Where to store the text for gain values (for later display).
|
|
|
|
* length Bytes left in buffer.
|
|
|
|
*/
|
|
|
|
long parse_replaygain_int(bool album, long gain, long peak,
|
|
|
|
struct mp3entry* entry, char* buffer, int length)
|
2006-07-20 10:35:20 +00:00
|
|
|
{
|
2007-08-25 10:25:13 +00:00
|
|
|
long len = 0;
|
2006-07-20 10:35:20 +00:00
|
|
|
|
2007-08-25 10:25:13 +00:00
|
|
|
if (buffer != NULL)
|
2006-07-20 10:35:20 +00:00
|
|
|
{
|
2007-08-25 10:25:13 +00:00
|
|
|
len = snprintf(buffer, length, "%d.%02d dB", gain / 512,
|
|
|
|
((abs(gain) & 0x01ff) * 100 + 256) / 512);
|
|
|
|
len++;
|
2006-07-20 10:35:20 +00:00
|
|
|
}
|
|
|
|
|
2007-08-25 10:25:13 +00:00
|
|
|
if (gain != 0)
|
2006-07-20 10:35:20 +00:00
|
|
|
{
|
2007-08-25 10:25:13 +00:00
|
|
|
gain = convert_gain(gain * FP_ONE / 512);
|
2006-07-20 10:35:20 +00:00
|
|
|
}
|
|
|
|
|
2007-08-25 10:25:13 +00:00
|
|
|
if (album)
|
2006-07-20 10:35:20 +00:00
|
|
|
{
|
2007-08-25 10:56:22 +00:00
|
|
|
entry->album_gain = gain;
|
|
|
|
entry->album_gain_string = buffer;
|
2006-07-20 10:35:20 +00:00
|
|
|
|
2007-08-25 10:56:22 +00:00
|
|
|
if (peak)
|
2007-08-25 10:25:13 +00:00
|
|
|
{
|
|
|
|
entry->album_peak = peak;
|
|
|
|
}
|
2006-07-20 10:35:20 +00:00
|
|
|
}
|
2007-08-25 10:25:13 +00:00
|
|
|
else
|
2006-07-20 10:35:20 +00:00
|
|
|
{
|
2007-08-25 10:56:22 +00:00
|
|
|
entry->track_gain = gain;
|
|
|
|
entry->track_gain_string = buffer;
|
2006-07-20 10:35:20 +00:00
|
|
|
|
2007-08-25 10:56:22 +00:00
|
|
|
if (peak)
|
2007-08-25 10:25:13 +00:00
|
|
|
{
|
|
|
|
entry->track_peak = peak;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return len;
|
2006-07-20 10:35:20 +00:00
|
|
|
}
|