Mercurial > audlegacy-plugins
comparison src/flac113/replaygain.c @ 103:117bc56d906b trunk
[svn] flac -> flac113
author | nenolod |
---|---|
date | Mon, 23 Oct 2006 19:51:39 -0700 |
parents | src/flac/replaygain.c@a19f24790f3c |
children |
comparison
equal
deleted
inserted
replaced
102:aff1cf3e86dd | 103:117bc56d906b |
---|---|
1 /* grabbag - Convenience lib for various routines common to several tools | |
2 * Copyright (C) 2002,2003,2004,2005,2006 Josh Coalson | |
3 * | |
4 * This program is free software; you can redistribute it and/or | |
5 * modify it under the terms of the GNU General Public License | |
6 * as published by the Free Software Foundation; either version 2 | |
7 * of the License, or (at your option) any later version. | |
8 * | |
9 * This program is distributed in the hope that it will be useful, | |
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 * GNU General Public License for more details. | |
13 * | |
14 * You should have received a copy of the GNU General Public License | |
15 * along with this program; if not, write to the Free Software | |
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |
17 */ | |
18 | |
19 #if HAVE_CONFIG_H | |
20 # include <config.h> | |
21 #endif | |
22 | |
23 #include "grabbag.h" | |
24 #include "replaygain_analysis.h" | |
25 #include "FLAC/assert.h" | |
26 #include "FLAC/metadata.h" | |
27 #include "FLAC/stream_decoder.h" | |
28 #include <locale.h> | |
29 #include <math.h> | |
30 #include <stdio.h> | |
31 #include <stdlib.h> | |
32 #include <string.h> | |
33 #if defined _MSC_VER || defined __MINGW32__ | |
34 #include <io.h> /* for chmod() */ | |
35 #endif | |
36 #include <sys/stat.h> /* for stat(), maybe chmod() */ | |
37 | |
38 #ifdef local_min | |
39 #undef local_min | |
40 #endif | |
41 #define local_min(a,b) ((a)<(b)?(a):(b)) | |
42 | |
43 #ifdef local_max | |
44 #undef local_max | |
45 #endif | |
46 #define local_max(a,b) ((a)>(b)?(a):(b)) | |
47 | |
48 static const char *reference_format_ = "%s=%2.1f dB"; | |
49 static const char *gain_format_ = "%s=%+2.2f dB"; | |
50 static const char *peak_format_ = "%s=%1.8f"; | |
51 | |
52 static double album_peak_, title_peak_; | |
53 | |
54 const unsigned GRABBAG__REPLAYGAIN_MAX_TAG_SPACE_REQUIRED = 190; | |
55 /* | |
56 FLAC__STREAM_METADATA_VORBIS_COMMENT_ENTRY_LENGTH_LEN/8 + 29 + 1 + 8 + | |
57 FLAC__STREAM_METADATA_VORBIS_COMMENT_ENTRY_LENGTH_LEN/8 + 21 + 1 + 10 + | |
58 FLAC__STREAM_METADATA_VORBIS_COMMENT_ENTRY_LENGTH_LEN/8 + 21 + 1 + 12 + | |
59 FLAC__STREAM_METADATA_VORBIS_COMMENT_ENTRY_LENGTH_LEN/8 + 21 + 1 + 10 + | |
60 FLAC__STREAM_METADATA_VORBIS_COMMENT_ENTRY_LENGTH_LEN/8 + 21 + 1 + 12 | |
61 */ | |
62 | |
63 const FLAC__byte * const GRABBAG__REPLAYGAIN_TAG_REFERENCE_LOUDNESS = (const FLAC__byte * const)"REPLAYGAIN_REFERENCE_LOUDNESS"; | |
64 const FLAC__byte * const GRABBAG__REPLAYGAIN_TAG_TITLE_GAIN = (const FLAC__byte * const)"REPLAYGAIN_TRACK_GAIN"; | |
65 const FLAC__byte * const GRABBAG__REPLAYGAIN_TAG_TITLE_PEAK = (const FLAC__byte * const)"REPLAYGAIN_TRACK_PEAK"; | |
66 const FLAC__byte * const GRABBAG__REPLAYGAIN_TAG_ALBUM_GAIN = (const FLAC__byte * const)"REPLAYGAIN_ALBUM_GAIN"; | |
67 const FLAC__byte * const GRABBAG__REPLAYGAIN_TAG_ALBUM_PEAK = (const FLAC__byte * const)"REPLAYGAIN_ALBUM_PEAK"; | |
68 | |
69 | |
70 static FLAC__bool get_file_stats_(const char *filename, struct stat *stats) | |
71 { | |
72 FLAC__ASSERT(0 != filename); | |
73 FLAC__ASSERT(0 != stats); | |
74 return (0 == stat(filename, stats)); | |
75 } | |
76 | |
77 static void set_file_stats_(const char *filename, struct stat *stats) | |
78 { | |
79 FLAC__ASSERT(0 != filename); | |
80 FLAC__ASSERT(0 != stats); | |
81 | |
82 (void)chmod(filename, stats->st_mode); | |
83 } | |
84 | |
85 static FLAC__bool append_tag_(FLAC__StreamMetadata *block, const char *format, const FLAC__byte *name, float value) | |
86 { | |
87 char buffer[256]; | |
88 char *saved_locale; | |
89 FLAC__StreamMetadata_VorbisComment_Entry entry; | |
90 | |
91 FLAC__ASSERT(0 != block); | |
92 FLAC__ASSERT(block->type == FLAC__METADATA_TYPE_VORBIS_COMMENT); | |
93 FLAC__ASSERT(0 != format); | |
94 FLAC__ASSERT(0 != name); | |
95 | |
96 buffer[sizeof(buffer)-1] = '\0'; | |
97 /* | |
98 * We need to save the old locale and switch to "C" because the locale | |
99 * influences the formatting of %f and we want it a certain way. | |
100 */ | |
101 saved_locale = setlocale(LC_ALL, 0); | |
102 setlocale(LC_ALL, "C"); | |
103 #if defined _MSC_VER || defined __MINGW32__ | |
104 _snprintf(buffer, sizeof(buffer)-1, format, name, value); | |
105 #else | |
106 snprintf(buffer, sizeof(buffer)-1, format, name, value); | |
107 #endif | |
108 setlocale(LC_ALL, saved_locale); | |
109 | |
110 entry.entry = (FLAC__byte *)buffer; | |
111 entry.length = strlen(buffer); | |
112 | |
113 return FLAC__metadata_object_vorbiscomment_append_comment(block, entry, /*copy=*/true); | |
114 } | |
115 | |
116 FLAC__bool grabbag__replaygain_is_valid_sample_frequency(unsigned sample_frequency) | |
117 { | |
118 static const unsigned valid_sample_rates[] = { | |
119 8000, | |
120 11025, | |
121 12000, | |
122 16000, | |
123 22050, | |
124 24000, | |
125 32000, | |
126 44100, | |
127 48000 | |
128 }; | |
129 static const unsigned n_valid_sample_rates = sizeof(valid_sample_rates) / sizeof(valid_sample_rates[0]); | |
130 | |
131 unsigned i; | |
132 | |
133 for(i = 0; i < n_valid_sample_rates; i++) | |
134 if(sample_frequency == valid_sample_rates[i]) | |
135 return true; | |
136 return false; | |
137 } | |
138 | |
139 FLAC__bool grabbag__replaygain_init(unsigned sample_frequency) | |
140 { | |
141 title_peak_ = album_peak_ = 0.0; | |
142 return InitGainAnalysis((long)sample_frequency) == INIT_GAIN_ANALYSIS_OK; | |
143 } | |
144 | |
145 FLAC__bool grabbag__replaygain_analyze(const FLAC__int32 * const input[], FLAC__bool is_stereo, unsigned bps, unsigned samples) | |
146 { | |
147 /* using a small buffer improves data locality; we'd like it to fit easily in the dcache */ | |
148 static Float_t lbuffer[2048], rbuffer[2048]; | |
149 static const unsigned nbuffer = sizeof(lbuffer) / sizeof(lbuffer[0]); | |
150 FLAC__int32 block_peak = 0, s; | |
151 unsigned i, j; | |
152 | |
153 FLAC__ASSERT(bps >= 4 && bps <= FLAC__REFERENCE_CODEC_MAX_BITS_PER_SAMPLE); | |
154 FLAC__ASSERT(FLAC__MIN_BITS_PER_SAMPLE == 4); | |
155 /* | |
156 * We use abs() on a FLAC__int32 which is undefined for the most negative value. | |
157 * If the reference codec ever handles 32bps we will have to write a special | |
158 * case here. | |
159 */ | |
160 FLAC__ASSERT(FLAC__REFERENCE_CODEC_MAX_BITS_PER_SAMPLE < 32); | |
161 | |
162 if(bps == 16) { | |
163 if(is_stereo) { | |
164 j = 0; | |
165 while(samples > 0) { | |
166 const unsigned n = local_min(samples, nbuffer); | |
167 for(i = 0; i < n; i++, j++) { | |
168 s = input[0][j]; | |
169 lbuffer[i] = (Float_t)s; | |
170 s = abs(s); | |
171 block_peak = local_max(block_peak, s); | |
172 | |
173 s = input[1][j]; | |
174 rbuffer[i] = (Float_t)s; | |
175 s = abs(s); | |
176 block_peak = local_max(block_peak, s); | |
177 } | |
178 samples -= n; | |
179 if(AnalyzeSamples(lbuffer, rbuffer, n, 2) != GAIN_ANALYSIS_OK) | |
180 return false; | |
181 } | |
182 } | |
183 else { | |
184 j = 0; | |
185 while(samples > 0) { | |
186 const unsigned n = local_min(samples, nbuffer); | |
187 for(i = 0; i < n; i++, j++) { | |
188 s = input[0][j]; | |
189 lbuffer[i] = (Float_t)s; | |
190 s = abs(s); | |
191 block_peak = local_max(block_peak, s); | |
192 } | |
193 samples -= n; | |
194 if(AnalyzeSamples(lbuffer, 0, n, 1) != GAIN_ANALYSIS_OK) | |
195 return false; | |
196 } | |
197 } | |
198 } | |
199 else { /* bps must be < 32 according to above assertion */ | |
200 const double scale = ( | |
201 (bps > 16)? | |
202 (double)1. / (double)(1u << (bps - 16)) : | |
203 (double)(1u << (16 - bps)) | |
204 ); | |
205 | |
206 if(is_stereo) { | |
207 j = 0; | |
208 while(samples > 0) { | |
209 const unsigned n = local_min(samples, nbuffer); | |
210 for(i = 0; i < n; i++, j++) { | |
211 s = input[0][j]; | |
212 lbuffer[i] = (Float_t)(scale * (double)s); | |
213 s = abs(s); | |
214 block_peak = local_max(block_peak, s); | |
215 | |
216 s = input[1][j]; | |
217 rbuffer[i] = (Float_t)(scale * (double)s); | |
218 s = abs(s); | |
219 block_peak = local_max(block_peak, s); | |
220 } | |
221 samples -= n; | |
222 if(AnalyzeSamples(lbuffer, rbuffer, n, 2) != GAIN_ANALYSIS_OK) | |
223 return false; | |
224 } | |
225 } | |
226 else { | |
227 j = 0; | |
228 while(samples > 0) { | |
229 const unsigned n = local_min(samples, nbuffer); | |
230 for(i = 0; i < n; i++, j++) { | |
231 s = input[0][j]; | |
232 lbuffer[i] = (Float_t)(scale * (double)s); | |
233 s = abs(s); | |
234 block_peak = local_max(block_peak, s); | |
235 } | |
236 samples -= n; | |
237 if(AnalyzeSamples(lbuffer, 0, n, 1) != GAIN_ANALYSIS_OK) | |
238 return false; | |
239 } | |
240 } | |
241 } | |
242 | |
243 { | |
244 const double peak_scale = (double)(1u << (bps - 1)); | |
245 double peak = (double)block_peak / peak_scale; | |
246 if(peak > title_peak_) | |
247 title_peak_ = peak; | |
248 if(peak > album_peak_) | |
249 album_peak_ = peak; | |
250 } | |
251 | |
252 return true; | |
253 } | |
254 | |
255 void grabbag__replaygain_get_album(float *gain, float *peak) | |
256 { | |
257 *gain = (float)GetAlbumGain(); | |
258 *peak = (float)album_peak_; | |
259 album_peak_ = 0.0; | |
260 } | |
261 | |
262 void grabbag__replaygain_get_title(float *gain, float *peak) | |
263 { | |
264 *gain = (float)GetTitleGain(); | |
265 *peak = (float)title_peak_; | |
266 title_peak_ = 0.0; | |
267 } | |
268 | |
269 | |
270 typedef struct { | |
271 unsigned channels; | |
272 unsigned bits_per_sample; | |
273 unsigned sample_rate; | |
274 FLAC__bool error; | |
275 } DecoderInstance; | |
276 | |
277 static FLAC__StreamDecoderWriteStatus write_callback_(const FLAC__StreamDecoder *decoder, const FLAC__Frame *frame, const FLAC__int32 * const buffer[], void *client_data) | |
278 { | |
279 DecoderInstance *instance = (DecoderInstance*)client_data; | |
280 const unsigned bits_per_sample = frame->header.bits_per_sample; | |
281 const unsigned channels = frame->header.channels; | |
282 const unsigned sample_rate = frame->header.sample_rate; | |
283 const unsigned samples = frame->header.blocksize; | |
284 | |
285 (void)decoder; | |
286 | |
287 if( | |
288 !instance->error && | |
289 (channels == 2 || channels == 1) && | |
290 bits_per_sample == instance->bits_per_sample && | |
291 channels == instance->channels && | |
292 sample_rate == instance->sample_rate | |
293 ) { | |
294 instance->error = !grabbag__replaygain_analyze(buffer, channels==2, bits_per_sample, samples); | |
295 } | |
296 else { | |
297 instance->error = true; | |
298 } | |
299 | |
300 if(!instance->error) | |
301 return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; | |
302 else | |
303 return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; | |
304 } | |
305 | |
306 static void metadata_callback_(const FLAC__StreamDecoder *decoder, const FLAC__StreamMetadata *metadata, void *client_data) | |
307 { | |
308 DecoderInstance *instance = (DecoderInstance*)client_data; | |
309 | |
310 (void)decoder; | |
311 | |
312 if(metadata->type == FLAC__METADATA_TYPE_STREAMINFO) { | |
313 instance->bits_per_sample = metadata->data.stream_info.bits_per_sample; | |
314 instance->channels = metadata->data.stream_info.channels; | |
315 instance->sample_rate = metadata->data.stream_info.sample_rate; | |
316 | |
317 if(instance->channels != 1 && instance->channels != 2) { | |
318 instance->error = true; | |
319 return; | |
320 } | |
321 | |
322 if(!grabbag__replaygain_is_valid_sample_frequency(instance->sample_rate)) { | |
323 instance->error = true; | |
324 return; | |
325 } | |
326 } | |
327 } | |
328 | |
329 static void error_callback_(const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderErrorStatus status, void *client_data) | |
330 { | |
331 DecoderInstance *instance = (DecoderInstance*)client_data; | |
332 | |
333 (void)decoder, (void)status; | |
334 | |
335 instance->error = true; | |
336 } | |
337 | |
338 const char *grabbag__replaygain_analyze_file(const char *filename, float *title_gain, float *title_peak) | |
339 { | |
340 DecoderInstance instance; | |
341 FLAC__StreamDecoder *decoder = FLAC__stream_decoder_new(); | |
342 | |
343 if(0 == decoder) | |
344 return "memory allocation error"; | |
345 | |
346 instance.error = false; | |
347 | |
348 /* It does these three by default but lets be explicit: */ | |
349 FLAC__stream_decoder_set_md5_checking(decoder, false); | |
350 FLAC__stream_decoder_set_metadata_ignore_all(decoder); | |
351 FLAC__stream_decoder_set_metadata_respond(decoder, FLAC__METADATA_TYPE_STREAMINFO); | |
352 | |
353 if(FLAC__stream_decoder_init_file(decoder, filename, write_callback_, metadata_callback_, error_callback_, &instance) != FLAC__STREAM_DECODER_INIT_STATUS_OK) { | |
354 FLAC__stream_decoder_delete(decoder); | |
355 return "initializing decoder"; | |
356 } | |
357 | |
358 if(!FLAC__stream_decoder_process_until_end_of_stream(decoder) || instance.error) { | |
359 FLAC__stream_decoder_delete(decoder); | |
360 return "decoding file"; | |
361 } | |
362 | |
363 FLAC__stream_decoder_delete(decoder); | |
364 | |
365 grabbag__replaygain_get_title(title_gain, title_peak); | |
366 | |
367 return 0; | |
368 } | |
369 | |
370 const char *grabbag__replaygain_store_to_vorbiscomment(FLAC__StreamMetadata *block, float album_gain, float album_peak, float title_gain, float title_peak) | |
371 { | |
372 const char *error; | |
373 | |
374 if(0 != (error = grabbag__replaygain_store_to_vorbiscomment_reference(block))) | |
375 return error; | |
376 | |
377 if(0 != (error = grabbag__replaygain_store_to_vorbiscomment_title(block, title_gain, title_peak))) | |
378 return error; | |
379 | |
380 if(0 != (error = grabbag__replaygain_store_to_vorbiscomment_album(block, album_gain, album_peak))) | |
381 return error; | |
382 | |
383 return 0; | |
384 } | |
385 | |
386 const char *grabbag__replaygain_store_to_vorbiscomment_reference(FLAC__StreamMetadata *block) | |
387 { | |
388 FLAC__ASSERT(0 != block); | |
389 FLAC__ASSERT(block->type == FLAC__METADATA_TYPE_VORBIS_COMMENT); | |
390 | |
391 if(FLAC__metadata_object_vorbiscomment_remove_entries_matching(block, (const char *)GRABBAG__REPLAYGAIN_TAG_REFERENCE_LOUDNESS) < 0) | |
392 return "memory allocation error"; | |
393 | |
394 if(!append_tag_(block, reference_format_, GRABBAG__REPLAYGAIN_TAG_REFERENCE_LOUDNESS, ReplayGainReferenceLoudness)) | |
395 return "memory allocation error"; | |
396 | |
397 return 0; | |
398 } | |
399 | |
400 const char *grabbag__replaygain_store_to_vorbiscomment_album(FLAC__StreamMetadata *block, float album_gain, float album_peak) | |
401 { | |
402 FLAC__ASSERT(0 != block); | |
403 FLAC__ASSERT(block->type == FLAC__METADATA_TYPE_VORBIS_COMMENT); | |
404 | |
405 if( | |
406 FLAC__metadata_object_vorbiscomment_remove_entries_matching(block, (const char *)GRABBAG__REPLAYGAIN_TAG_ALBUM_GAIN) < 0 || | |
407 FLAC__metadata_object_vorbiscomment_remove_entries_matching(block, (const char *)GRABBAG__REPLAYGAIN_TAG_ALBUM_PEAK) < 0 | |
408 ) | |
409 return "memory allocation error"; | |
410 | |
411 if( | |
412 !append_tag_(block, gain_format_, GRABBAG__REPLAYGAIN_TAG_ALBUM_GAIN, album_gain) || | |
413 !append_tag_(block, peak_format_, GRABBAG__REPLAYGAIN_TAG_ALBUM_PEAK, album_peak) | |
414 ) | |
415 return "memory allocation error"; | |
416 | |
417 return 0; | |
418 } | |
419 | |
420 const char *grabbag__replaygain_store_to_vorbiscomment_title(FLAC__StreamMetadata *block, float title_gain, float title_peak) | |
421 { | |
422 FLAC__ASSERT(0 != block); | |
423 FLAC__ASSERT(block->type == FLAC__METADATA_TYPE_VORBIS_COMMENT); | |
424 | |
425 if( | |
426 FLAC__metadata_object_vorbiscomment_remove_entries_matching(block, (const char *)GRABBAG__REPLAYGAIN_TAG_TITLE_GAIN) < 0 || | |
427 FLAC__metadata_object_vorbiscomment_remove_entries_matching(block, (const char *)GRABBAG__REPLAYGAIN_TAG_TITLE_PEAK) < 0 | |
428 ) | |
429 return "memory allocation error"; | |
430 | |
431 if( | |
432 !append_tag_(block, gain_format_, GRABBAG__REPLAYGAIN_TAG_TITLE_GAIN, title_gain) || | |
433 !append_tag_(block, peak_format_, GRABBAG__REPLAYGAIN_TAG_TITLE_PEAK, title_peak) | |
434 ) | |
435 return "memory allocation error"; | |
436 | |
437 return 0; | |
438 } | |
439 | |
440 static const char *store_to_file_pre_(const char *filename, FLAC__Metadata_Chain **chain, FLAC__StreamMetadata **block) | |
441 { | |
442 FLAC__Metadata_Iterator *iterator; | |
443 const char *error; | |
444 FLAC__bool found_vc_block = false; | |
445 | |
446 if(0 == (*chain = FLAC__metadata_chain_new())) | |
447 return "memory allocation error"; | |
448 | |
449 if(!FLAC__metadata_chain_read(*chain, filename)) { | |
450 error = FLAC__Metadata_ChainStatusString[FLAC__metadata_chain_status(*chain)]; | |
451 FLAC__metadata_chain_delete(*chain); | |
452 return error; | |
453 } | |
454 | |
455 if(0 == (iterator = FLAC__metadata_iterator_new())) { | |
456 FLAC__metadata_chain_delete(*chain); | |
457 return "memory allocation error"; | |
458 } | |
459 | |
460 FLAC__metadata_iterator_init(iterator, *chain); | |
461 | |
462 do { | |
463 *block = FLAC__metadata_iterator_get_block(iterator); | |
464 if((*block)->type == FLAC__METADATA_TYPE_VORBIS_COMMENT) | |
465 found_vc_block = true; | |
466 } while(!found_vc_block && FLAC__metadata_iterator_next(iterator)); | |
467 | |
468 if(!found_vc_block) { | |
469 /* create a new block */ | |
470 *block = FLAC__metadata_object_new(FLAC__METADATA_TYPE_VORBIS_COMMENT); | |
471 if(0 == *block) { | |
472 FLAC__metadata_chain_delete(*chain); | |
473 FLAC__metadata_iterator_delete(iterator); | |
474 return "memory allocation error"; | |
475 } | |
476 while(FLAC__metadata_iterator_next(iterator)) | |
477 ; | |
478 if(!FLAC__metadata_iterator_insert_block_after(iterator, *block)) { | |
479 error = FLAC__Metadata_ChainStatusString[FLAC__metadata_chain_status(*chain)]; | |
480 FLAC__metadata_chain_delete(*chain); | |
481 FLAC__metadata_iterator_delete(iterator); | |
482 return error; | |
483 } | |
484 /* iterator is left pointing to new block */ | |
485 FLAC__ASSERT(FLAC__metadata_iterator_get_block(iterator) == *block); | |
486 } | |
487 | |
488 FLAC__metadata_iterator_delete(iterator); | |
489 | |
490 FLAC__ASSERT(0 != *block); | |
491 FLAC__ASSERT((*block)->type == FLAC__METADATA_TYPE_VORBIS_COMMENT); | |
492 | |
493 return 0; | |
494 } | |
495 | |
496 static const char *store_to_file_post_(const char *filename, FLAC__Metadata_Chain *chain, FLAC__bool preserve_modtime) | |
497 { | |
498 struct stat stats; | |
499 const FLAC__bool have_stats = get_file_stats_(filename, &stats); | |
500 | |
501 (void)grabbag__file_change_stats(filename, /*read_only=*/false); | |
502 | |
503 FLAC__metadata_chain_sort_padding(chain); | |
504 if(!FLAC__metadata_chain_write(chain, /*use_padding=*/true, preserve_modtime)) { | |
505 FLAC__metadata_chain_delete(chain); | |
506 return FLAC__Metadata_ChainStatusString[FLAC__metadata_chain_status(chain)]; | |
507 } | |
508 | |
509 FLAC__metadata_chain_delete(chain); | |
510 | |
511 if(have_stats) | |
512 set_file_stats_(filename, &stats); | |
513 | |
514 return 0; | |
515 } | |
516 | |
517 const char *grabbag__replaygain_store_to_file(const char *filename, float album_gain, float album_peak, float title_gain, float title_peak, FLAC__bool preserve_modtime) | |
518 { | |
519 FLAC__Metadata_Chain *chain; | |
520 FLAC__StreamMetadata *block; | |
521 const char *error; | |
522 | |
523 if(0 != (error = store_to_file_pre_(filename, &chain, &block))) | |
524 return error; | |
525 | |
526 if(0 != (error = grabbag__replaygain_store_to_vorbiscomment(block, album_gain, album_peak, title_gain, title_peak))) { | |
527 FLAC__metadata_chain_delete(chain); | |
528 return error; | |
529 } | |
530 | |
531 if(0 != (error = store_to_file_post_(filename, chain, preserve_modtime))) | |
532 return error; | |
533 | |
534 return 0; | |
535 } | |
536 | |
537 const char *grabbag__replaygain_store_to_file_reference(const char *filename, FLAC__bool preserve_modtime) | |
538 { | |
539 FLAC__Metadata_Chain *chain; | |
540 FLAC__StreamMetadata *block; | |
541 const char *error; | |
542 | |
543 if(0 != (error = store_to_file_pre_(filename, &chain, &block))) | |
544 return error; | |
545 | |
546 if(0 != (error = grabbag__replaygain_store_to_vorbiscomment_reference(block))) { | |
547 FLAC__metadata_chain_delete(chain); | |
548 return error; | |
549 } | |
550 | |
551 if(0 != (error = store_to_file_post_(filename, chain, preserve_modtime))) | |
552 return error; | |
553 | |
554 return 0; | |
555 } | |
556 | |
557 const char *grabbag__replaygain_store_to_file_album(const char *filename, float album_gain, float album_peak, FLAC__bool preserve_modtime) | |
558 { | |
559 FLAC__Metadata_Chain *chain; | |
560 FLAC__StreamMetadata *block; | |
561 const char *error; | |
562 | |
563 if(0 != (error = store_to_file_pre_(filename, &chain, &block))) | |
564 return error; | |
565 | |
566 if(0 != (error = grabbag__replaygain_store_to_vorbiscomment_album(block, album_gain, album_peak))) { | |
567 FLAC__metadata_chain_delete(chain); | |
568 return error; | |
569 } | |
570 | |
571 if(0 != (error = store_to_file_post_(filename, chain, preserve_modtime))) | |
572 return error; | |
573 | |
574 return 0; | |
575 } | |
576 | |
577 const char *grabbag__replaygain_store_to_file_title(const char *filename, float title_gain, float title_peak, FLAC__bool preserve_modtime) | |
578 { | |
579 FLAC__Metadata_Chain *chain; | |
580 FLAC__StreamMetadata *block; | |
581 const char *error; | |
582 | |
583 if(0 != (error = store_to_file_pre_(filename, &chain, &block))) | |
584 return error; | |
585 | |
586 if(0 != (error = grabbag__replaygain_store_to_vorbiscomment_title(block, title_gain, title_peak))) { | |
587 FLAC__metadata_chain_delete(chain); | |
588 return error; | |
589 } | |
590 | |
591 if(0 != (error = store_to_file_post_(filename, chain, preserve_modtime))) | |
592 return error; | |
593 | |
594 return 0; | |
595 } | |
596 | |
597 static FLAC__bool parse_double_(const FLAC__StreamMetadata_VorbisComment_Entry *entry, double *val) | |
598 { | |
599 char s[32], *end; | |
600 const char *p, *q; | |
601 double v; | |
602 | |
603 FLAC__ASSERT(0 != entry); | |
604 FLAC__ASSERT(0 != val); | |
605 | |
606 p = (const char *)entry->entry; | |
607 q = strchr(p, '='); | |
608 if(0 == q) | |
609 return false; | |
610 q++; | |
611 memset(s, 0, sizeof(s)-1); | |
612 strncpy(s, q, local_min(sizeof(s)-1, entry->length - (q-p))); | |
613 | |
614 v = strtod(s, &end); | |
615 if(end == s) | |
616 return false; | |
617 | |
618 *val = v; | |
619 return true; | |
620 } | |
621 | |
622 FLAC__bool grabbag__replaygain_load_from_vorbiscomment(const FLAC__StreamMetadata *block, FLAC__bool album_mode, FLAC__bool strict, double *reference, double *gain, double *peak) | |
623 { | |
624 int reference_offset, gain_offset, peak_offset; | |
625 | |
626 FLAC__ASSERT(0 != block); | |
627 FLAC__ASSERT(0 != reference); | |
628 FLAC__ASSERT(0 != gain); | |
629 FLAC__ASSERT(0 != peak); | |
630 FLAC__ASSERT(block->type == FLAC__METADATA_TYPE_VORBIS_COMMENT); | |
631 | |
632 /* Default to current level until overridden by a detected tag; this | |
633 * will always be true until we change replaygain_analysis.c | |
634 */ | |
635 *reference = ReplayGainReferenceLoudness; | |
636 | |
637 if(0 <= (reference_offset = FLAC__metadata_object_vorbiscomment_find_entry_from(block, /*offset=*/0, (const char *)GRABBAG__REPLAYGAIN_TAG_REFERENCE_LOUDNESS))) | |
638 (void)parse_double_(block->data.vorbis_comment.comments + reference_offset, reference); | |
639 | |
640 if(0 > (gain_offset = FLAC__metadata_object_vorbiscomment_find_entry_from(block, /*offset=*/0, (const char *)(album_mode? GRABBAG__REPLAYGAIN_TAG_ALBUM_GAIN : GRABBAG__REPLAYGAIN_TAG_TITLE_GAIN)))) | |
641 return !strict && grabbag__replaygain_load_from_vorbiscomment(block, !album_mode, /*strict=*/true, reference, gain, peak); | |
642 if(0 > (peak_offset = FLAC__metadata_object_vorbiscomment_find_entry_from(block, /*offset=*/0, (const char *)(album_mode? GRABBAG__REPLAYGAIN_TAG_ALBUM_PEAK : GRABBAG__REPLAYGAIN_TAG_TITLE_PEAK)))) | |
643 return !strict && grabbag__replaygain_load_from_vorbiscomment(block, !album_mode, /*strict=*/true, reference, gain, peak); | |
644 | |
645 if(!parse_double_(block->data.vorbis_comment.comments + gain_offset, gain)) | |
646 return !strict && grabbag__replaygain_load_from_vorbiscomment(block, !album_mode, /*strict=*/true, reference, gain, peak); | |
647 if(!parse_double_(block->data.vorbis_comment.comments + peak_offset, peak)) | |
648 return !strict && grabbag__replaygain_load_from_vorbiscomment(block, !album_mode, /*strict=*/true, reference, gain, peak); | |
649 | |
650 return true; | |
651 } | |
652 | |
653 double grabbag__replaygain_compute_scale_factor(double peak, double gain, double preamp, FLAC__bool prevent_clipping) | |
654 { | |
655 double scale; | |
656 FLAC__ASSERT(peak >= 0.0); | |
657 gain += preamp; | |
658 scale = (float) pow(10.0, gain * 0.05); | |
659 if(prevent_clipping && peak > 0.0) { | |
660 const double max_scale = (float)(1.0 / peak); | |
661 if(scale > max_scale) | |
662 scale = max_scale; | |
663 } | |
664 return scale; | |
665 } |