2005-07-24 15:25:13 +00:00
|
|
|
/***************************************************************************
|
|
|
|
* __________ __ ___.
|
|
|
|
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
|
|
|
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
|
|
|
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
|
|
|
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
|
|
|
* \/ \/ \/ \/ \/
|
|
|
|
* $Id$
|
|
|
|
*
|
|
|
|
* Copyright (C) 2005 Magnus Holmgren
|
|
|
|
*
|
|
|
|
* All files in this archive are subject to the GNU General Public License.
|
|
|
|
* See the file COPYING in the source tree root for full license agreement.
|
|
|
|
*
|
|
|
|
* 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>
|
|
|
|
#include "id3.h"
|
2005-07-24 15:25:13 +00:00
|
|
|
#include "debug.h"
|
|
|
|
|
2006-07-20 10:35:20 +00:00
|
|
|
/* Type of channel for RVA2 frame. There are more than this defined in the spec
|
|
|
|
but we don't use them. */
|
|
|
|
#define MASTER_CHANNEL 1
|
|
|
|
|
2005-07-24 15:25:13 +00:00
|
|
|
/* The fixed point math routines (with the exception of fp_atof) are based
|
|
|
|
* on oMathFP by Dan Carter (http://orbisstudios.com).
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* 12 bits of precision gives fairly accurate result, but still allows a
|
|
|
|
* compact implementation. The math code supports up to 13...
|
|
|
|
*/
|
|
|
|
|
|
|
|
#define FP_BITS (12)
|
|
|
|
#define FP_MASK ((1 << FP_BITS) - 1)
|
|
|
|
#define FP_ONE (1 << FP_BITS)
|
|
|
|
#define FP_TWO (2 << FP_BITS)
|
|
|
|
#define FP_HALF (1 << (FP_BITS - 1))
|
|
|
|
#define FP_LN2 ( 45426 >> (16 - FP_BITS))
|
|
|
|
#define FP_LN2_INV ( 94548 >> (16 - FP_BITS))
|
|
|
|
#define FP_EXP_ZERO ( 10922 >> (16 - FP_BITS))
|
|
|
|
#define FP_EXP_ONE ( -182 >> (16 - FP_BITS))
|
|
|
|
#define FP_EXP_TWO ( 4 >> (16 - FP_BITS))
|
|
|
|
#define FP_INF (0x7fffffff)
|
|
|
|
#define FP_LN10 (150902 >> (16 - FP_BITS))
|
|
|
|
|
|
|
|
#define FP_MAX_DIGITS (4)
|
|
|
|
#define FP_MAX_DIGITS_INT (10000)
|
|
|
|
|
|
|
|
#define FP_FAST_MUL_DIV
|
|
|
|
|
|
|
|
#ifdef FP_FAST_MUL_DIV
|
|
|
|
|
|
|
|
/* These macros can easily overflow, but they are good enough for our uses,
|
|
|
|
* and saves some code.
|
|
|
|
*/
|
|
|
|
#define fp_mul(x, y) (((x) * (y)) >> FP_BITS)
|
|
|
|
#define fp_div(x, y) (((x) << FP_BITS) / (y))
|
|
|
|
|
|
|
|
#else
|
|
|
|
|
|
|
|
static long fp_mul(long x, long y)
|
|
|
|
{
|
|
|
|
long x_neg = 0;
|
|
|
|
long y_neg = 0;
|
|
|
|
long rc;
|
|
|
|
|
|
|
|
if ((x == 0) || (y == 0))
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (x < 0)
|
|
|
|
{
|
|
|
|
x_neg = 1;
|
|
|
|
x = -x;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (y < 0)
|
|
|
|
{
|
|
|
|
y_neg = 1;
|
|
|
|
y = -y;
|
|
|
|
}
|
|
|
|
|
|
|
|
rc = (((x >> FP_BITS) * (y >> FP_BITS)) << FP_BITS)
|
|
|
|
+ (((x & FP_MASK) * (y & FP_MASK)) >> FP_BITS)
|
|
|
|
+ ((x & FP_MASK) * (y >> FP_BITS))
|
|
|
|
+ ((x >> FP_BITS) * (y & FP_MASK));
|
|
|
|
|
|
|
|
if ((x_neg ^ y_neg) == 1)
|
|
|
|
{
|
|
|
|
rc = -rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
static long fp_div(long x, long y)
|
|
|
|
{
|
|
|
|
long x_neg = 0;
|
|
|
|
long y_neg = 0;
|
|
|
|
long shifty;
|
|
|
|
long rc;
|
|
|
|
int msb = 0;
|
|
|
|
int lsb = 0;
|
|
|
|
|
|
|
|
if (x == 0)
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (y == 0)
|
|
|
|
{
|
|
|
|
return (x < 0) ? -FP_INF : FP_INF;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (x < 0)
|
|
|
|
{
|
|
|
|
x_neg = 1;
|
|
|
|
x = -x;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (y < 0)
|
|
|
|
{
|
|
|
|
y_neg = 1;
|
|
|
|
y = -y;
|
|
|
|
}
|
|
|
|
|
|
|
|
while ((x & (1 << (30 - msb))) == 0)
|
|
|
|
{
|
|
|
|
msb++;
|
|
|
|
}
|
|
|
|
|
|
|
|
while ((y & (1 << lsb)) == 0)
|
|
|
|
{
|
|
|
|
lsb++;
|
|
|
|
}
|
|
|
|
|
|
|
|
shifty = FP_BITS - (msb + lsb);
|
|
|
|
rc = ((x << msb) / (y >> lsb));
|
|
|
|
|
|
|
|
if (shifty > 0)
|
|
|
|
{
|
|
|
|
rc <<= shifty;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
rc >>= -shifty;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((x_neg ^ y_neg) == 1)
|
|
|
|
{
|
|
|
|
rc = -rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif /* FP_FAST_MUL_DIV */
|
|
|
|
|
|
|
|
static long fp_exp(long x)
|
|
|
|
{
|
|
|
|
long k;
|
|
|
|
long z;
|
|
|
|
long R;
|
|
|
|
long xp;
|
|
|
|
|
|
|
|
if (x == 0)
|
|
|
|
{
|
|
|
|
return FP_ONE;
|
|
|
|
}
|
|
|
|
|
|
|
|
k = (fp_mul(abs(x), FP_LN2_INV) + FP_HALF) & ~FP_MASK;
|
|
|
|
|
|
|
|
if (x < 0)
|
|
|
|
{
|
|
|
|
k = -k;
|
|
|
|
}
|
|
|
|
|
|
|
|
x -= fp_mul(k, FP_LN2);
|
|
|
|
z = fp_mul(x, x);
|
|
|
|
R = FP_TWO + fp_mul(z, FP_EXP_ZERO + fp_mul(z, FP_EXP_ONE
|
|
|
|
+ fp_mul(z, FP_EXP_TWO)));
|
|
|
|
xp = FP_ONE + fp_div(fp_mul(FP_TWO, x), R - x);
|
|
|
|
|
|
|
|
if (k < 0)
|
|
|
|
{
|
|
|
|
k = FP_ONE >> (-k >> FP_BITS);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
k = FP_ONE << (k >> FP_BITS);
|
|
|
|
}
|
|
|
|
|
|
|
|
return fp_mul(k, xp);
|
|
|
|
}
|
|
|
|
|
|
|
|
static long fp_exp10(long x)
|
|
|
|
{
|
|
|
|
if (x == 0)
|
|
|
|
{
|
|
|
|
return FP_ONE;
|
|
|
|
}
|
|
|
|
|
|
|
|
return fp_exp(fp_mul(FP_LN10, x));
|
|
|
|
}
|
|
|
|
|
|
|
|
static long fp_atof(const char* s, int precision)
|
|
|
|
{
|
|
|
|
long int_part = 0;
|
|
|
|
long int_one = 1 << precision;
|
|
|
|
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;
|
|
|
|
|
|
|
|
while ((*s != '\0') && isspace(*s))
|
|
|
|
{
|
|
|
|
s++;
|
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
{
|
|
|
|
if (gain != 0)
|
|
|
|
{
|
2006-07-20 10:35:20 +00:00
|
|
|
/* Don't allow unreasonably low or high gain changes.
|
2005-07-24 15:25:13 +00:00
|
|
|
* Our math code can't handle it properly anyway. :)
|
|
|
|
*/
|
2006-04-06 20:33:35 +00:00
|
|
|
if (gain < (-48 * FP_ONE))
|
2005-07-24 15:25:13 +00:00
|
|
|
{
|
2006-04-06 20:33:35 +00:00
|
|
|
gain = -48 * FP_ONE;
|
2005-07-24 15:25:13 +00:00
|
|
|
}
|
2006-07-20 10:35:20 +00:00
|
|
|
|
2005-07-24 15:25:13 +00:00
|
|
|
if (gain > (17 * FP_ONE))
|
|
|
|
{
|
|
|
|
gain = 17 * FP_ONE;
|
|
|
|
}
|
|
|
|
|
|
|
|
gain = fp_exp10(gain / 20) << (24 - FP_BITS);
|
|
|
|
}
|
2006-07-20 10:35:20 +00:00
|
|
|
|
2005-07-24 15:25:13 +00:00
|
|
|
return gain;
|
|
|
|
}
|
|
|
|
|
2005-08-11 18:56:20 +00:00
|
|
|
long get_replaygain_int(long int_gain)
|
|
|
|
{
|
|
|
|
long gain = 0;
|
2006-07-20 10:35:20 +00:00
|
|
|
|
2005-08-11 18:56:20 +00:00
|
|
|
if (int_gain)
|
|
|
|
{
|
|
|
|
gain = convert_gain(int_gain * FP_ONE / 100);
|
|
|
|
}
|
2006-07-20 10:35:20 +00:00
|
|
|
|
2005-08-11 18:56:20 +00:00
|
|
|
return gain;
|
|
|
|
}
|
|
|
|
|
2005-07-24 15:25:13 +00:00
|
|
|
long get_replaygain(const char* str)
|
|
|
|
{
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
long get_replaypeak(const char* str)
|
|
|
|
{
|
|
|
|
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
|
|
|
|
2006-07-20 10:35:20 +00:00
|
|
|
/* Check for a ReplayGain tag conforming to the "VorbisGain standard". If
|
2005-10-03 18:35:02 +00:00
|
|
|
* found, set the mp3entry accordingly. buffer is where to store the text
|
2006-07-20 10:35:20 +00:00
|
|
|
* contents of the gain tags; up to length bytes (including end nil) can be
|
|
|
|
* written. Returns number of bytes written to the tag text buffer, or zero
|
2005-10-03 18:35:02 +00:00
|
|
|
* if no ReplayGain tag was found (or nothing was copied to the buffer for
|
2005-07-27 11:54:33 +00:00
|
|
|
* other reasons).
|
|
|
|
*/
|
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)
|
|
|
|
{
|
2005-10-03 18:35:02 +00:00
|
|
|
strncpy(buffer, value, len);
|
2005-07-27 11:54:33 +00:00
|
|
|
buffer[len] = 0;
|
|
|
|
*p = buffer;
|
|
|
|
return len + 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
2006-07-20 10:35:20 +00:00
|
|
|
|
|
|
|
static long get_rva_values(const char *frame, long *gain, long *peak,
|
|
|
|
char **string, char *buffer, int length)
|
|
|
|
{
|
|
|
|
long value, len;
|
|
|
|
int negative = 0;
|
|
|
|
char tmpbuf[10];
|
|
|
|
int peakbits, peakbytes, shift;
|
|
|
|
unsigned long peakvalue = 0;
|
|
|
|
|
|
|
|
value = 256 * ((unsigned char)*frame) + ((unsigned char)*(frame + 1));
|
|
|
|
if (value & 0x8000)
|
|
|
|
{
|
|
|
|
value = -(value | ~0xFFFF);
|
|
|
|
negative = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
len = snprintf(tmpbuf, sizeof(tmpbuf), "%s%d.%02d dB", negative ? "-" : "",
|
|
|
|
value / 512, (value & 0x1FF) * 195 / 1000);
|
|
|
|
|
|
|
|
*gain = get_replaygain(tmpbuf);
|
|
|
|
|
|
|
|
len = MIN(len, length - 1);
|
|
|
|
if (len > 1)
|
|
|
|
{
|
|
|
|
strncpy(buffer, tmpbuf, len);
|
|
|
|
buffer[len] = 0;
|
|
|
|
*string = buffer;
|
|
|
|
}
|
|
|
|
|
|
|
|
frame += 2;
|
|
|
|
peakbits = *(unsigned char *)frame++;
|
|
|
|
peakbytes = MIN(4, (peakbits + 7) >> 3);
|
|
|
|
shift = ((8 - (peakbits & 7)) & 7) + (4 - peakbytes) * 8;
|
|
|
|
|
|
|
|
for (; peakbytes; peakbytes--)
|
|
|
|
{
|
|
|
|
peakvalue <<= 8;
|
|
|
|
peakvalue += (unsigned long)*frame++;
|
|
|
|
}
|
|
|
|
|
|
|
|
peakvalue <<= shift;
|
|
|
|
|
|
|
|
if (peakbits > 32)
|
|
|
|
peakvalue += (unsigned long)*frame >> (8 - shift);
|
|
|
|
|
|
|
|
snprintf(tmpbuf, sizeof(tmpbuf), "%d.%06d", peakvalue >> 31,
|
|
|
|
(peakvalue & ~(1 << 31)) / 2147);
|
|
|
|
|
|
|
|
*peak = get_replaypeak(tmpbuf);
|
|
|
|
|
|
|
|
return len + 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
long parse_replaygain_rva(const char* key, const char* value,
|
|
|
|
struct mp3entry* entry, char* buffer, int length)
|
|
|
|
{
|
2006-07-21 20:15:01 +00:00
|
|
|
/* Values will be overwritten if they already exist. This gives priority to
|
|
|
|
replaygain in RVA2 fields over TXXX fields for ID3v2.4. */
|
|
|
|
if ((strcasecmp(key, "track") == 0) && *value == MASTER_CHANNEL)
|
2006-07-20 10:35:20 +00:00
|
|
|
{
|
|
|
|
return get_rva_values(value + 1, &(entry->track_gain), &(entry->track_peak),
|
|
|
|
&(entry->track_gain_string), buffer, length);
|
|
|
|
}
|
2006-07-21 20:15:01 +00:00
|
|
|
else if ((strcasecmp(key, "album") == 0) && *value == MASTER_CHANNEL)
|
2006-07-20 10:35:20 +00:00
|
|
|
{
|
|
|
|
return get_rva_values(value + 1, &(entry->album_gain), &(entry->album_peak),
|
|
|
|
&(entry->album_gain_string), buffer, length);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|