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