/*************************************************************************** * __________ __ ___. * 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 #include #include #include #include #include #include #include #include "id3.h" #include "debug.h" /* 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 /* 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; } return sign * ((int_part * int_one) + (((int64_t) frac_part * int_one) / frac_max_int)); } static long convert_gain(long gain) { if (gain != 0) { /* Don't allow unreasonably low or high gain changes. * Our math code can't handle it properly anyway. :) */ if (gain < (-48 * FP_ONE)) { gain = -48 * FP_ONE; } if (gain > (17 * FP_ONE)) { gain = 17 * FP_ONE; } gain = fp_exp10(gain / 20) << (24 - FP_BITS); } return gain; } long get_replaygain_int(long int_gain) { long gain = 0; if (int_gain) { gain = convert_gain(int_gain * FP_ONE / 100); } return gain; } long get_replaygain(const char* str) { long gain = 0; if (str) { gain = fp_atof(str, FP_BITS); gain = convert_gain(gain); } return gain; } long get_replaypeak(const char* str) { long peak = 0; if (str) { peak = fp_atof(str, 24); } return peak; } /* Check for a ReplayGain tag conforming to the "VorbisGain standard". If * found, set the mp3entry accordingly. buffer is where to store the text * 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 * if no ReplayGain tag was found (or nothing was copied to the buffer for * other reasons). */ long parse_replaygain(const char* key, const char* value, struct mp3entry* entry, char* buffer, int length) { char **p = NULL; if (((strcasecmp(key, "replaygain_track_gain") == 0) || (strcasecmp(key, "rg_radio") == 0)) && !entry->track_gain) { entry->track_gain = get_replaygain(value); p = &(entry->track_gain_string); } else if (((strcasecmp(key, "replaygain_album_gain") == 0) || (strcasecmp(key, "rg_audiophile") == 0)) && !entry->album_gain) { entry->album_gain = get_replaygain(value); p = &(entry->album_gain_string); } else if (((strcasecmp(key, "replaygain_track_peak") == 0) || (strcasecmp(key, "rg_peak") == 0)) && !entry->track_peak) { entry->track_peak = get_replaypeak(value); } else if ((strcasecmp(key, "replaygain_album_peak") == 0) && !entry->album_peak) { entry->album_peak = get_replaypeak(value); } if (p) { int len = strlen(value); len = MIN(len, length - 1); /* A few characters just isn't interesting... */ if (len > 1) { strncpy(buffer, value, len); buffer[len] = 0; *p = buffer; return len + 1; } } return 0; } 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) { /* 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) { return get_rva_values(value + 1, &(entry->track_gain), &(entry->track_peak), &(entry->track_gain_string), buffer, length); } else if ((strcasecmp(key, "album") == 0) && *value == MASTER_CHANNEL) { return get_rva_values(value + 1, &(entry->album_gain), &(entry->album_peak), &(entry->album_gain_string), buffer, length); } return 0; }