int AnalyzeSamplesInterleaved(char * samples, long num_samples, int num_channels){ double leftbuf[MAXSAMPLES], rightbuf[MAXSAMPLES]; long i; long totSamples = num_samples; long nSamples = num_samples; signed short int * samp = (signed short int *)samples; int result = GAIN_ANALYSIS_ERROR; /* NOTES: * leftbuf and rightbuf are arrays of doubles * samp is a short (16-bit) integer * inf is the input file * totSamples is the total number of samples remaining in the input file * MAXSAMPLES is the maximum number of samples to send to AnalyzeSamples at * once */ while (totSamples > 0) { if (totSamples > MAXSAMPLES) nSamples = MAXSAMPLES; else nSamples = totSamples; if (num_channels == 2) { for (i = 0; i < nSamples; i++) { leftbuf[i] = *samp++; /* default conversion from short to double */ rightbuf[i] = *samp++; } result = AnalyzeSamples(leftbuf, rightbuf, nSamples, 2); if (result != GAIN_ANALYSIS_OK) return result; } // end stereo else { /* Just one channel (mono) */ for (i = 0; i < nSamples; i++) { leftbuf[i] = *samp++; } result = AnalyzeSamples(leftbuf, NULL, nSamples, 1); if (result != GAIN_ANALYSIS_OK) return result; } // end just mono totSamples -= nSamples; } // end while return result; } /* AnalyzeSamplesInterleaved */
FLAC__bool grabbag__replaygain_analyze(const FLAC__int32 * const input[], FLAC__bool is_stereo, unsigned bps, unsigned samples) { /* 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 <= FLAC__REFERENCE_CODEC_MAX_BITS_PER_SAMPLE); FLAC__ASSERT(FLAC__MIN_BITS_PER_SAMPLE == 4); /* * 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) { j = 0; while(samples > 0) { const unsigned n = local_min(samples, nbuffer); 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); 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) return false; } } else { j = 0; while(samples > 0) { const unsigned n = local_min(samples, nbuffer); 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 { /* bps must be < 32 according to above assertion */ const double scale = ( (bps > 16)? (double)1. / (double)(1u << (bps - 16)) : (double)(1u << (16 - bps)) ); if(is_stereo) { j = 0; while(samples > 0) { const unsigned n = local_min(samples, nbuffer); 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); 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) return false; } } else { j = 0; while(samples > 0) { const unsigned n = local_min(samples, nbuffer); 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; } } } { 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; }
bool SFB::Audio::ReplayGainAnalyzer::AnalyzeURL(CFURLRef url, CFErrorRef *error) { if(nullptr == url) return false; auto decoder = Decoder::CreateDecoderForURL(url, error); if(!decoder || !decoder->Open(error)) return false; AudioStreamBasicDescription inputFormat = decoder->GetFormat(); // Higher sampling rates aren't natively supported but are handled via resampling int32_t decoderSampleRate = (int32_t)inputFormat.mSampleRate; bool validSampleRate = EvenMultipleSampleRateIsSupported(decoderSampleRate); if(!validSampleRate) { if(error) { SFB::CFString description = CFCopyLocalizedString(CFSTR("The file “%@” does not contain audio at a supported sample rate."), ""); SFB::CFString failureReason = CFCopyLocalizedString(CFSTR("Only sample rates of 8.0 KHz, 11.025 KHz, 12.0 KHz, 16.0 KHz, 22.05 KHz, 24.0 KHz, 32.0 KHz, 44.1 KHz, 48 KHz and multiples are supported."), ""); SFB::CFString recoverySuggestion = CFCopyLocalizedString(CFSTR("The file's extension may not match the file's type."), ""); *error = CreateErrorForURL(ReplayGainAnalyzer::ErrorDomain, ReplayGainAnalyzer::FileFormatNotSupportedError, description, url, failureReason, recoverySuggestion); } return false; } Float64 replayGainSampleRate = GetBestReplayGainSampleRateForSampleRate(decoderSampleRate); if(!(1 == inputFormat.mChannelsPerFrame || 2 == inputFormat.mChannelsPerFrame)) { if(error) { SFB::CFString description = CFCopyLocalizedString(CFSTR("The file “%@” does not contain mono or stereo audio."), ""); SFB::CFString failureReason = CFCopyLocalizedString(CFSTR("Only mono or stereo files supported"), ""); SFB::CFString recoverySuggestion = CFCopyLocalizedString(CFSTR("The file's extension may not match the file's type."), ""); *error = CreateErrorForURL(ReplayGainAnalyzer::ErrorDomain, ReplayGainAnalyzer::FileFormatNotSupportedError, description, url, failureReason, recoverySuggestion); } return false; } AudioStreamBasicDescription outputFormat = { .mFormatID = kAudioFormatLinearPCM, .mFormatFlags = kAudioFormatFlagsNativeFloatPacked | kAudioFormatFlagIsNonInterleaved, .mReserved = 0, .mSampleRate = replayGainSampleRate, .mChannelsPerFrame = inputFormat.mChannelsPerFrame, .mBitsPerChannel = 32, .mBytesPerPacket = 4, .mBytesPerFrame = 4, .mFramesPerPacket = 1 }; if(!SetSampleRate((int32_t)outputFormat.mSampleRate)) { if(error) { SFB::CFString description = CFCopyLocalizedString(CFSTR("The file “%@” does not contain audio at a supported sample rate."), ""); SFB::CFString failureReason = CFCopyLocalizedString(CFSTR("Only sample rates of 8.0 KHz, 11.025 KHz, 12.0 KHz, 16.0 KHz, 22.05 KHz, 24.0 KHz, 32.0 KHz, 44.1 KHz, 48 KHz and multiples are supported."), ""); SFB::CFString recoverySuggestion = CFCopyLocalizedString(CFSTR("The file's extension may not match the file's type."), ""); *error = CreateErrorForURL(ReplayGainAnalyzer::ErrorDomain, ReplayGainAnalyzer::FileFormatNotSupportedError, description, url, failureReason, recoverySuggestion); } return false; } // Converter takes ownership of decoder Converter converter(std::move(decoder), outputFormat); if(!converter.Open(error)) return false; const UInt32 bufferSizeFrames = 512; BufferList outputBuffer(outputFormat, bufferSizeFrames); bool isStereo = (2 == outputFormat.mChannelsPerFrame); for(;;) { UInt32 frameCount = converter.ConvertAudio(outputBuffer, bufferSizeFrames); if(0 == frameCount) break; // Find the peak sample magnitude float lpeak, rpeak; vDSP_maxmgv((const float *)outputBuffer->mBuffers[0].mData, 1, &lpeak, frameCount); if(isStereo) { vDSP_maxmgv((const float *)outputBuffer->mBuffers[1].mData, 1, &rpeak, frameCount); priv->trackPeak = std::max(priv->trackPeak, std::max(lpeak, rpeak)); } else priv->trackPeak = std::max(priv->trackPeak, lpeak); // The replay gain analyzer expects 16-bit sample size passed as floats const float scale = 1u << 15; vDSP_vsmul((const float *)outputBuffer->mBuffers[0].mData, 1, &scale, (float *)outputBuffer->mBuffers[0].mData, 1, frameCount); if(isStereo) { vDSP_vsmul((const float *)outputBuffer->mBuffers[1].mData, 1, &scale, (float *)outputBuffer->mBuffers[1].mData, 1, frameCount); AnalyzeSamples((const float *)outputBuffer->mBuffers[0].mData, (const float *)outputBuffer->mBuffers[1].mData, frameCount, true); } else AnalyzeSamples((const float *)outputBuffer->mBuffers[0].mData, nullptr, frameCount, false); } priv->albumPeak = std::max(priv->albumPeak, priv->trackPeak); return true; } bool SFB::Audio::ReplayGainAnalyzer::GetTrackGain(float& trackGain) { if(!analyzeResult(priv->A, sizeof(priv->A) / sizeof(*(priv->A)), trackGain)) return false; for(uint32_t i = 0; i < sizeof(priv->A) / sizeof(*(priv->A)); ++i) { priv->B[i] += priv->A[i]; priv->A[i] = 0; } priv->Zero(); priv->totsamp = 0; priv->lsum = priv->rsum = 0.; return true; } bool SFB::Audio::ReplayGainAnalyzer::GetTrackPeak(float& trackPeak) { trackPeak = priv->trackPeak; priv->trackPeak = 0.; return true; }
static void CalcReplayGain ( const char* filename, gain_info_t* G ) { FILE* fp; float buffl [NO]; float buffr [NO]; Int16_t buff [NO] [2]; size_t i; size_t len; size_t lastlen = 0; unsigned int max = 0; float level; float mult; if ((fp = pipeopen ( "mppdec --silent --scale 0.5 --gain 0 --raw - - < #", filename)) == NULL) { stderr_printf ( "Can't decode '%s'\n", filename ); exit (9); } memset ( buff, 0, sizeof(buff) ); G->Silence = 0; lastlen = len = fread (buff, 4, NO, fp); for ( i = 0; i < len; i++ ) { buffl [i] = 2. * buff [i] [0]; buffr [i] = 2. * buff [i] [1]; if ( abs(buff[i][0]) > max ) max = abs(buff[i][0]); if ( abs(buff[i][1]) > max ) max = abs(buff[i][1]); } AnalyzeSamples ( buffl, buffr, len, 2 ); level = 0.; mult = 1.; for ( i = 0; i < len; i++ ) { level += mult * (buff [i] [0] * buff [i] [0] + buff [i] [1] * buff [i] [1]); mult *= 0.95; } level = 2*sqrt(level * 0.05); if ( level > LEVEL_THR ) G->Silence |= 2; sh ( NULL, level ); while (( len = fread (buff, 4, NO, fp) ) > 0 ) { lastlen = len; for ( i = 0; i < len; i++ ) { buffl [i] = 2. * buff [i] [0]; buffr [i] = 2. * buff [i] [1]; if ( abs(buff[i][0]) > max ) max = abs(buff[i][0]); if ( abs(buff[i][1]) > max ) max = abs(buff[i][1]); } AnalyzeSamples ( buffl, buffr, len, 2 ); } level = 0.; mult = 1.; for ( i = 1; i <= NO; i++ ) { int idx = (lastlen + NO - i) % NO; level += mult * (buff [idx] [0] * buff [idx] [0] + buff [idx] [1] * buff [idx] [1]); mult *= 0.95; } level = 2*sqrt(level * 0.05); if ( level > LEVEL_THR ) G->Silence |= 1; sh(filename,level); PCLOSE (fp); #if 0 GetTitleDynamics (); #endif G -> FileName = filename; G -> TitleGain = GetTitleGain (); G -> TitlePeak = 2 * max + 1; G -> AlbumGain = GetAlbumGain (); G -> AlbumPeak = G->AlbumPeak < G->TitlePeak ? G->TitlePeak : G->AlbumPeak; return; }