From fcb502f7ca1d87a0e78aa3465e90db47b73f90ef Mon Sep 17 00:00:00 2001 From: Josh Coalson Date: Wed, 30 Oct 2002 06:19:54 +0000 Subject: [PATCH] fix bug in rebuffering loop, add peak analysis, more fine-grained tagging functions --- src/share/replaygain/replaygain.c | 272 ++++++++++++++++++++++++++++++-------- 1 file changed, 218 insertions(+), 54 deletions(-) diff --git a/src/share/replaygain/replaygain.c b/src/share/replaygain/replaygain.c index eac1ac3..6cc2e3a 100644 --- a/src/share/replaygain/replaygain.c +++ b/src/share/replaygain/replaygain.c @@ -16,19 +16,30 @@ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ +#include "share/file_utils.h" #include "share/replaygain.h" #include "share/gain_analysis.h" #include "FLAC/assert.h" #include "FLAC/file_decoder.h" #include "FLAC/metadata.h" #include +#include #include +#if defined _MSC_VER || defined __MINGW32__ +#include /* for chmod() */ +#endif +#include /* for stat(), maybe chmod() */ #ifdef local_min #undef local_min #endif #define local_min(a,b) ((a)<(b)?(a):(b)) +#ifdef local_max +#undef local_max +#endif +#define local_max(a,b) ((a)>(b)?(a):(b)) + static const FLAC__byte *tag_title_gain_ = "REPLAYGAIN_TRACK_GAIN"; static const FLAC__byte *tag_title_peak_ = "REPLAYGAIN_TRACK_PEAK"; static const FLAC__byte *tag_album_gain_ = "REPLAYGAIN_ALBUM_GAIN"; @@ -36,6 +47,32 @@ static const FLAC__byte *tag_album_peak_ = "REPLAYGAIN_ALBUM_PEAK"; static const char *peak_format_ = "%s=%1.8f"; static const char *gain_format_ = "%s=%+2.2f dB"; +static double album_peak_, title_peak_; + +REPLAYGAIN_API const unsigned FLAC__REPLAYGAIN_MAX_TAG_SPACE_REQUIRED = 148; +/* + FLAC__STREAM_METADATA_VORBIS_COMMENT_ENTRY_LENGTH_LEN/8 + 21 + 1 + 10 + + FLAC__STREAM_METADATA_VORBIS_COMMENT_ENTRY_LENGTH_LEN/8 + 21 + 1 + 12 + + FLAC__STREAM_METADATA_VORBIS_COMMENT_ENTRY_LENGTH_LEN/8 + 21 + 1 + 10 + + FLAC__STREAM_METADATA_VORBIS_COMMENT_ENTRY_LENGTH_LEN/8 + 21 + 1 + 12 +*/ + + +static FLAC__bool get_file_stats_(const char *filename, struct stat *stats) +{ + FLAC__ASSERT(0 != filename); + FLAC__ASSERT(0 != stats); + return (0 == stat(filename, stats)); +} + +static void set_file_stats_(const char *filename, struct stat *stats) +{ + FLAC__ASSERT(0 != filename); + FLAC__ASSERT(0 != stats); + + (void)chmod(filename, stats->st_mode); +} + static FLAC__bool append_tag_(FLAC__StreamMetadata *block, const char *format, const FLAC__byte *name, float value) { char buffer[256]; @@ -80,6 +117,7 @@ REPLAYGAIN_API FLAC__bool FLAC__replaygain_is_valid_sample_frequency(unsigned sa REPLAYGAIN_API FLAC__bool FLAC__replaygain_init(unsigned sample_frequency) { + title_peak_ = album_peak_ = 0.0; return InitGainAnalysis((long)sample_frequency) == INIT_GAIN_ANALYSIS_OK; } @@ -88,11 +126,17 @@ REPLAYGAIN_API FLAC__bool FLAC__replaygain_analyze(const FLAC__int32 * const inp /* using a small buffer improves data locality; we'd like it to fit easily in the dcache */ static Float_t lbuffer[2048], rbuffer[2048]; static const unsigned nbuffer = sizeof(lbuffer) / sizeof(lbuffer[0]); + FLAC__int32 block_peak = 0, s; unsigned i, j; - FLAC__ASSERT(bps >= 4 && bps <= 32); + FLAC__ASSERT(bps >= 4 && bps <= FLAC__REFERENCE_CODEC_MAX_BITS_PER_SAMPLE); FLAC__ASSERT(FLAC__MIN_BITS_PER_SAMPLE == 4); - FLAC__ASSERT(FLAC__MAX_BITS_PER_SAMPLE == 32); + /* + * We use abs() on a FLAC__int32 which is undefined for the most negative value. + * If the reference codec ever handles 32bps we will have to write a special + * case here. + */ + FLAC__ASSERT(FLAC__REFERENCE_CODEC_MAX_BITS_PER_SAMPLE < 32); if(bps == 16) { if(is_stereo) { @@ -100,8 +144,15 @@ REPLAYGAIN_API FLAC__bool FLAC__replaygain_analyze(const FLAC__int32 * const inp while(samples > 0) { const unsigned n = local_min(samples, nbuffer); for(i = 0; i < n; i++, j++) { - lbuffer[i] = (Float_t)input[0][j]; - rbuffer[i] = (Float_t)input[1][j]; + s = input[0][j]; + lbuffer[i] = (Float_t)s; + s = abs(s); + block_peak = local_max(block_peak, s); + + s = input[1][j]; + rbuffer[i] = (Float_t)s; + s = abs(s); + block_peak = local_max(block_peak, s); } samples -= n; if(AnalyzeSamples(lbuffer, rbuffer, n, 2) != GAIN_ANALYSIS_OK) @@ -112,15 +163,19 @@ REPLAYGAIN_API FLAC__bool FLAC__replaygain_analyze(const FLAC__int32 * const inp j = 0; while(samples > 0) { const unsigned n = local_min(samples, nbuffer); - for(i = 0; i < n; i++, j++) - lbuffer[i] = (Float_t)input[0][j]; + for(i = 0; i < n; i++, j++) { + s = input[0][j]; + lbuffer[i] = (Float_t)s; + s = abs(s); + block_peak = local_max(block_peak, s); + } samples -= n; if(AnalyzeSamples(lbuffer, 0, n, 1) != GAIN_ANALYSIS_OK) return false; } } } - else { + else { /* bps must be < 32 according to above assertion */ const double scale = ( (bps > 16)? (double)1. / (double)(1u << (bps - 16)) : @@ -132,8 +187,15 @@ REPLAYGAIN_API FLAC__bool FLAC__replaygain_analyze(const FLAC__int32 * const inp while(samples > 0) { const unsigned n = local_min(samples, nbuffer); for(i = 0; i < n; i++, j++) { - lbuffer[i] = (Float_t)(scale * (double)input[0][j]); - rbuffer[i] = (Float_t)(scale * (double)input[1][j]); + s = input[0][j]; + lbuffer[i] = (Float_t)(scale * (double)s); + s = abs(s); + block_peak = local_max(block_peak, s); + + s = input[1][j]; + rbuffer[i] = (Float_t)(scale * (double)s); + s = abs(s); + block_peak = local_max(block_peak, s); } samples -= n; if(AnalyzeSamples(lbuffer, rbuffer, n, 2) != GAIN_ANALYSIS_OK) @@ -144,8 +206,12 @@ REPLAYGAIN_API FLAC__bool FLAC__replaygain_analyze(const FLAC__int32 * const inp j = 0; while(samples > 0) { const unsigned n = local_min(samples, nbuffer); - for(i = 0; i < n; i++, j++) - lbuffer[i] = (Float_t)(scale * (double)input[0][j]); + for(i = 0; i < n; i++, j++) { + s = input[0][j]; + lbuffer[i] = (Float_t)(scale * (double)s); + s = abs(s); + block_peak = local_max(block_peak, s); + } samples -= n; if(AnalyzeSamples(lbuffer, 0, n, 1) != GAIN_ANALYSIS_OK) return false; @@ -153,17 +219,30 @@ REPLAYGAIN_API FLAC__bool FLAC__replaygain_analyze(const FLAC__int32 * const inp } } + { + const double peak_scale = (double)(1u << (bps - 1)); + double peak = (double)block_peak / peak_scale; + if(peak > title_peak_) + title_peak_ = peak; + if(peak > album_peak_) + album_peak_ = peak; + } + return true; } -REPLAYGAIN_API float FLAC__replaygain_get_album_gain() +REPLAYGAIN_API void FLAC__replaygain_get_album(float *gain, float *peak) { - return (float)GetAlbumGain(); + *gain = (float)GetAlbumGain(); + *peak = (float)album_peak_; + album_peak_ = 0.0; } -REPLAYGAIN_API float FLAC__replaygain_get_title_gain() +REPLAYGAIN_API void FLAC__replaygain_get_title(float *gain, float *peak) { - return (float)GetTitleGain(); + *gain = (float)GetTitleGain(); + *peak = (float)title_peak_; + title_peak_ = 0.0; } @@ -171,7 +250,6 @@ typedef struct { unsigned channels; unsigned bits_per_sample; unsigned sample_rate; - FLAC__int32 peak; FLAC__bool error; } DecoderInstance; @@ -182,7 +260,6 @@ static FLAC__StreamDecoderWriteStatus write_callback_(const FLAC__FileDecoder *d const unsigned channels = frame->header.channels; const unsigned sample_rate = frame->header.sample_rate; const unsigned samples = frame->header.blocksize; - unsigned channel, sample; (void)decoder; @@ -193,11 +270,6 @@ static FLAC__StreamDecoderWriteStatus write_callback_(const FLAC__FileDecoder *d channels == instance->channels && sample_rate == instance->sample_rate ) { - for(channel = 0; channel < channels; channel++) - for(sample = 0; sample < samples; sample++) - if(instance->peak < buffer[channel][sample]) - instance->peak = buffer[channel][sample]; - instance->error = !FLAC__replaygain_analyze(buffer, channels==2, bits_per_sample, samples); } else { @@ -250,7 +322,6 @@ REPLAYGAIN_API const char *FLAC__replaygain_analyze_file(const char *filename, f if(0 == decoder) return "memory allocation error"; - instance.peak = 0; instance.error = false; /* It does these three by default but lets be explicit: */ @@ -276,28 +347,36 @@ REPLAYGAIN_API const char *FLAC__replaygain_analyze_file(const char *filename, f FLAC__file_decoder_delete(decoder); - *title_gain = FLAC__replaygain_get_title_gain(); - *title_peak = (double)instance.peak / (double)(1u << (instance.bits_per_sample-1)); + FLAC__replaygain_get_title(title_gain, title_peak); return 0; } REPLAYGAIN_API const char *FLAC__replaygain_store_to_vorbiscomment(FLAC__StreamMetadata *block, float album_gain, float album_peak, float title_gain, float title_peak) { + const char *error; + + if(0 != (error = FLAC__replaygain_store_to_vorbiscomment_title(block, title_gain, title_peak))) + return error; + + if(0 != (error = FLAC__replaygain_store_to_vorbiscomment_album(block, album_gain, album_peak))) + return error; + + return 0; +} + +REPLAYGAIN_API const char *FLAC__replaygain_store_to_vorbiscomment_album(FLAC__StreamMetadata *block, float album_gain, float album_peak) +{ FLAC__ASSERT(0 != block); FLAC__ASSERT(block->type == FLAC__METADATA_TYPE_VORBIS_COMMENT); if( - FLAC__metadata_object_vorbiscomment_remove_entries_matching(block, tag_title_gain_) < 0 || - FLAC__metadata_object_vorbiscomment_remove_entries_matching(block, tag_title_peak_) < 0 || FLAC__metadata_object_vorbiscomment_remove_entries_matching(block, tag_album_gain_) < 0 || FLAC__metadata_object_vorbiscomment_remove_entries_matching(block, tag_album_peak_) < 0 ) return "memory allocation error"; if( - !append_tag_(block, peak_format_, tag_title_peak_, title_peak) || - !append_tag_(block, gain_format_, tag_title_gain_, title_gain) || !append_tag_(block, peak_format_, tag_album_peak_, album_peak) || !append_tag_(block, gain_format_, tag_album_gain_, album_gain) ) @@ -306,66 +385,88 @@ REPLAYGAIN_API const char *FLAC__replaygain_store_to_vorbiscomment(FLAC__StreamM return 0; } -REPLAYGAIN_API const char *FLAC__replaygain_store_to_file(const char *filename, float album_gain, float album_peak, float title_gain, float title_peak, FLAC__bool preserve_modtime) +REPLAYGAIN_API const char *FLAC__replaygain_store_to_vorbiscomment_title(FLAC__StreamMetadata *block, float title_gain, float title_peak) +{ + FLAC__ASSERT(0 != block); + FLAC__ASSERT(block->type == FLAC__METADATA_TYPE_VORBIS_COMMENT); + + if( + FLAC__metadata_object_vorbiscomment_remove_entries_matching(block, tag_title_gain_) < 0 || + FLAC__metadata_object_vorbiscomment_remove_entries_matching(block, tag_title_peak_) < 0 + ) + return "memory allocation error"; + + if( + !append_tag_(block, peak_format_, tag_title_peak_, title_peak) || + !append_tag_(block, gain_format_, tag_title_gain_, title_gain) + ) + return "memory allocation error"; + + return 0; +} + +static const char *store_to_file_pre_(const char *filename, FLAC__Metadata_Chain **chain, FLAC__StreamMetadata **block) { - FLAC__Metadata_Chain *chain; FLAC__Metadata_Iterator *iterator; - FLAC__StreamMetadata *block; const char *error; FLAC__bool found_vc_block = false; - if(0 == (chain = FLAC__metadata_chain_new())) + if(0 == (*chain = FLAC__metadata_chain_new())) return "memory allocation error"; - if(!FLAC__metadata_chain_read(chain, filename)) { - error = FLAC__Metadata_ChainStatusString[FLAC__metadata_chain_status(chain)]; - FLAC__metadata_chain_delete(chain); + if(!FLAC__metadata_chain_read(*chain, filename)) { + error = FLAC__Metadata_ChainStatusString[FLAC__metadata_chain_status(*chain)]; + FLAC__metadata_chain_delete(*chain); return error; } if(0 == (iterator = FLAC__metadata_iterator_new())) { - FLAC__metadata_chain_delete(chain); + FLAC__metadata_chain_delete(*chain); return "memory allocation error"; } - FLAC__metadata_iterator_init(iterator, chain); + FLAC__metadata_iterator_init(iterator, *chain); do { - block = FLAC__metadata_iterator_get_block(iterator); - if(block->type == FLAC__METADATA_TYPE_VORBIS_COMMENT) + *block = FLAC__metadata_iterator_get_block(iterator); + if((*block)->type == FLAC__METADATA_TYPE_VORBIS_COMMENT) found_vc_block = true; } while(!found_vc_block && FLAC__metadata_iterator_next(iterator)); if(!found_vc_block) { /* create a new block */ - block = FLAC__metadata_object_new(FLAC__METADATA_TYPE_VORBIS_COMMENT); - if(0 == block) { - FLAC__metadata_chain_delete(chain); + *block = FLAC__metadata_object_new(FLAC__METADATA_TYPE_VORBIS_COMMENT); + if(0 == *block) { + FLAC__metadata_chain_delete(*chain); FLAC__metadata_iterator_delete(iterator); return "memory allocation error"; } while(FLAC__metadata_iterator_next(iterator)) ; - if(!FLAC__metadata_iterator_insert_block_after(iterator, block)) { - error = FLAC__Metadata_ChainStatusString[FLAC__metadata_chain_status(chain)]; - FLAC__metadata_chain_delete(chain); + if(!FLAC__metadata_iterator_insert_block_after(iterator, *block)) { + error = FLAC__Metadata_ChainStatusString[FLAC__metadata_chain_status(*chain)]; + FLAC__metadata_chain_delete(*chain); FLAC__metadata_iterator_delete(iterator); return error; } /* iterator is left pointing to new block */ - FLAC__ASSERT(FLAC__metadata_iterator_get_block(iterator) == block); + FLAC__ASSERT(FLAC__metadata_iterator_get_block(iterator) == *block); } FLAC__metadata_iterator_delete(iterator); - FLAC__ASSERT(0 != block); - FLAC__ASSERT(block->type == FLAC__METADATA_TYPE_VORBIS_COMMENT); + FLAC__ASSERT(0 != *block); + FLAC__ASSERT((*block)->type == FLAC__METADATA_TYPE_VORBIS_COMMENT); - if(0 != (error = FLAC__replaygain_store_to_vorbiscomment(block, album_gain, album_peak, title_gain, title_peak))) { - FLAC__metadata_chain_delete(chain); - FLAC__metadata_iterator_delete(iterator); - return error; - } + return 0; +} + +static const char *store_to_file_post_(const char *filename, FLAC__Metadata_Chain *chain, FLAC__bool preserve_modtime) +{ + struct stat stats; + const FLAC__bool have_stats = get_file_stats_(filename, &stats); + + (void)FLAC__file_utils_change_stats(filename, /*read_only=*/false); FLAC__metadata_chain_sort_padding(chain); if(!FLAC__metadata_chain_write(chain, /*use_padding=*/true, preserve_modtime)) { @@ -375,5 +476,68 @@ REPLAYGAIN_API const char *FLAC__replaygain_store_to_file(const char *filename, FLAC__metadata_chain_delete(chain); + if(have_stats) + set_file_stats_(filename, &stats); + + return 0; +} + +REPLAYGAIN_API const char *FLAC__replaygain_store_to_file(const char *filename, float album_gain, float album_peak, float title_gain, float title_peak, FLAC__bool preserve_modtime) +{ + FLAC__Metadata_Chain *chain; + FLAC__StreamMetadata *block; + const char *error; + + if(0 != (error = store_to_file_pre_(filename, &chain, &block))) + return error; + + if(0 != (error = FLAC__replaygain_store_to_vorbiscomment(block, album_gain, album_peak, title_gain, title_peak))) { + FLAC__metadata_chain_delete(chain); + return error; + } + + if(0 != (error = store_to_file_post_(filename, chain, preserve_modtime))) + return error; + + return 0; +} + +REPLAYGAIN_API const char *FLAC__replaygain_store_to_file_album(const char *filename, float album_gain, float album_peak, FLAC__bool preserve_modtime) +{ + FLAC__Metadata_Chain *chain; + FLAC__StreamMetadata *block; + const char *error; + + if(0 != (error = store_to_file_pre_(filename, &chain, &block))) + return error; + + if(0 != (error = FLAC__replaygain_store_to_vorbiscomment_album(block, album_gain, album_peak))) { + FLAC__metadata_chain_delete(chain); + return error; + } + + if(0 != (error = store_to_file_post_(filename, chain, preserve_modtime))) + return error; + + return 0; +} + +REPLAYGAIN_API const char *FLAC__replaygain_store_to_file_title(const char *filename, float title_gain, float title_peak, FLAC__bool preserve_modtime) +{ + FLAC__Metadata_Chain *chain; + FLAC__StreamMetadata *block; + const char *error; + + if(0 != (error = store_to_file_pre_(filename, &chain, &block))) + return error; + + if(0 != (error = FLAC__replaygain_store_to_vorbiscomment_title(block, title_gain, title_peak))) { + FLAC__metadata_chain_delete(chain); + return error; + } + + if(0 != (error = store_to_file_post_(filename, chain, preserve_modtime))) + return error; + return 0; } -- 2.7.4