diff options
Diffstat (limited to 'lib/rbcodec/metadata/replaygain.c')
-rw-r--r-- | lib/rbcodec/metadata/replaygain.c | 222 |
1 files changed, 222 insertions, 0 deletions
diff --git a/lib/rbcodec/metadata/replaygain.c b/lib/rbcodec/metadata/replaygain.c new file mode 100644 index 0000000000..a178321385 --- /dev/null +++ b/lib/rbcodec/metadata/replaygain.c | |||
@@ -0,0 +1,222 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id$ | ||
9 | * | ||
10 | * Copyright (C) 2005 Magnus Holmgren | ||
11 | * | ||
12 | * This program is free software; you can redistribute it and/or | ||
13 | * modify it under the terms of the GNU General Public License | ||
14 | * as published by the Free Software Foundation; either version 2 | ||
15 | * of the License, or (at your option) any later version. | ||
16 | * | ||
17 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
18 | * KIND, either express or implied. | ||
19 | * | ||
20 | ****************************************************************************/ | ||
21 | |||
22 | #include <ctype.h> | ||
23 | #include <math.h> | ||
24 | #include <stdbool.h> | ||
25 | #include <stdio.h> | ||
26 | #include <stdlib.h> | ||
27 | #include <inttypes.h> | ||
28 | #include "strlcpy.h" | ||
29 | #include "strcasecmp.h" | ||
30 | #include "system.h" | ||
31 | #include "metadata.h" | ||
32 | #include "debug.h" | ||
33 | #include "replaygain.h" | ||
34 | #include "fixedpoint.h" | ||
35 | |||
36 | #define FP_BITS (12) | ||
37 | #define FP_ONE (1 << FP_BITS) | ||
38 | #define FP_MIN (-48 * FP_ONE) | ||
39 | #define FP_MAX ( 17 * FP_ONE) | ||
40 | |||
41 | void replaygain_itoa(char* buffer, int length, long int_gain) | ||
42 | { | ||
43 | /* int_gain uses Q19.12 format. */ | ||
44 | int one = abs(int_gain) >> FP_BITS; | ||
45 | int cent = ((abs(int_gain) & 0x0fff) * 100 + (FP_ONE/2)) >> FP_BITS; | ||
46 | snprintf(buffer, length, "%s%d.%02d dB", (int_gain<0) ? "-":"", one, cent); | ||
47 | } | ||
48 | |||
49 | static long fp_atof(const char* s, int precision) | ||
50 | { | ||
51 | long int_part = 0; | ||
52 | long int_one = BIT_N(precision); | ||
53 | long frac_part = 0; | ||
54 | long frac_count = 0; | ||
55 | long frac_max = ((precision * 4) + 12) / 13; | ||
56 | long frac_max_int = 1; | ||
57 | long sign = 1; | ||
58 | bool point = false; | ||
59 | |||
60 | while ((*s != '\0') && isspace(*s)) | ||
61 | { | ||
62 | s++; | ||
63 | } | ||
64 | |||
65 | if (*s == '-') | ||
66 | { | ||
67 | sign = -1; | ||
68 | s++; | ||
69 | } | ||
70 | else if (*s == '+') | ||
71 | { | ||
72 | s++; | ||
73 | } | ||
74 | |||
75 | while (*s != '\0') | ||
76 | { | ||
77 | if (*s == '.') | ||
78 | { | ||
79 | if (point) | ||
80 | { | ||
81 | break; | ||
82 | } | ||
83 | |||
84 | point = true; | ||
85 | } | ||
86 | else if (isdigit(*s)) | ||
87 | { | ||
88 | if (point) | ||
89 | { | ||
90 | if (frac_count < frac_max) | ||
91 | { | ||
92 | frac_part = frac_part * 10 + (*s - '0'); | ||
93 | frac_count++; | ||
94 | frac_max_int *= 10; | ||
95 | } | ||
96 | } | ||
97 | else | ||
98 | { | ||
99 | int_part = int_part * 10 + (*s - '0'); | ||
100 | } | ||
101 | } | ||
102 | else | ||
103 | { | ||
104 | break; | ||
105 | } | ||
106 | |||
107 | s++; | ||
108 | } | ||
109 | |||
110 | while (frac_count < frac_max) | ||
111 | { | ||
112 | frac_part *= 10; | ||
113 | frac_count++; | ||
114 | frac_max_int *= 10; | ||
115 | } | ||
116 | |||
117 | return sign * ((int_part * int_one) | ||
118 | + (((int64_t) frac_part * int_one) / frac_max_int)); | ||
119 | } | ||
120 | |||
121 | static long convert_gain(long gain) | ||
122 | { | ||
123 | /* Don't allow unreasonably low or high gain changes. | ||
124 | * Our math code can't handle it properly anyway. :) */ | ||
125 | gain = MAX(gain, FP_MIN); | ||
126 | gain = MIN(gain, FP_MAX); | ||
127 | |||
128 | return fp_factor(gain, FP_BITS) << (24 - FP_BITS); | ||
129 | } | ||
130 | |||
131 | /* Get the sample scale factor in Q19.12 format from a gain value. Returns 0 | ||
132 | * for no gain. | ||
133 | * | ||
134 | * str Gain in dB as a string. E.g., "-3.45 dB"; the "dB" part is ignored. | ||
135 | */ | ||
136 | static long get_replaygain(const char* str) | ||
137 | { | ||
138 | return fp_atof(str, FP_BITS); | ||
139 | } | ||
140 | |||
141 | /* Get the peak volume in Q7.24 format. | ||
142 | * | ||
143 | * str Peak volume. Full scale is specified as "1.0". Returns 0 for no peak. | ||
144 | */ | ||
145 | static long get_replaypeak(const char* str) | ||
146 | { | ||
147 | return fp_atof(str, 24); | ||
148 | } | ||
149 | |||
150 | /* Get a sample scale factor in Q7.24 format from a gain value. | ||
151 | * | ||
152 | * int_gain Gain in dB, multiplied by 100. | ||
153 | */ | ||
154 | long get_replaygain_int(long int_gain) | ||
155 | { | ||
156 | return convert_gain(int_gain * FP_ONE / 100); | ||
157 | } | ||
158 | |||
159 | /* Parse a ReplayGain tag conforming to the "VorbisGain standard". If a | ||
160 | * valid tag is found, update mp3entry struct accordingly. Existing values | ||
161 | * are not overwritten. | ||
162 | * | ||
163 | * key Name of the tag. | ||
164 | * value Value of the tag. | ||
165 | * entry mp3entry struct to update. | ||
166 | */ | ||
167 | void parse_replaygain(const char* key, const char* value, | ||
168 | struct mp3entry* entry) | ||
169 | { | ||
170 | if (((strcasecmp(key, "replaygain_track_gain") == 0) || | ||
171 | (strcasecmp(key, "rg_radio") == 0)) && | ||
172 | !entry->track_gain) | ||
173 | { | ||
174 | entry->track_level = get_replaygain(value); | ||
175 | entry->track_gain = convert_gain(entry->track_level); | ||
176 | } | ||
177 | else if (((strcasecmp(key, "replaygain_album_gain") == 0) || | ||
178 | (strcasecmp(key, "rg_audiophile") == 0)) && | ||
179 | !entry->album_gain) | ||
180 | { | ||
181 | entry->album_level = get_replaygain(value); | ||
182 | entry->album_gain = convert_gain(entry->album_level); | ||
183 | } | ||
184 | else if (((strcasecmp(key, "replaygain_track_peak") == 0) || | ||
185 | (strcasecmp(key, "rg_peak") == 0)) && | ||
186 | !entry->track_peak) | ||
187 | { | ||
188 | entry->track_peak = get_replaypeak(value); | ||
189 | } | ||
190 | else if ((strcasecmp(key, "replaygain_album_peak") == 0) && | ||
191 | !entry->album_peak) | ||
192 | { | ||
193 | entry->album_peak = get_replaypeak(value); | ||
194 | } | ||
195 | } | ||
196 | |||
197 | /* Set ReplayGain values from integers. Existing values are not overwritten. | ||
198 | * | ||
199 | * album If true, set album values, otherwise set track values. | ||
200 | * gain Gain value in dB, multiplied by 512. 0 for no gain. | ||
201 | * peak Peak volume in Q7.24 format, where 1.0 is full scale. 0 for no | ||
202 | * peak volume. | ||
203 | * entry mp3entry struct to update. | ||
204 | */ | ||
205 | void parse_replaygain_int(bool album, long gain, long peak, | ||
206 | struct mp3entry* entry) | ||
207 | { | ||
208 | gain = gain * FP_ONE / 512; | ||
209 | |||
210 | if (album) | ||
211 | { | ||
212 | entry->album_level = gain; | ||
213 | entry->album_gain = convert_gain(gain); | ||
214 | entry->album_peak = peak; | ||
215 | } | ||
216 | else | ||
217 | { | ||
218 | entry->track_level = gain; | ||
219 | entry->track_gain = convert_gain(gain); | ||
220 | entry->track_peak = peak; | ||
221 | } | ||
222 | } | ||