From 962640197222611c14e87ec00cfd1fe858054b83 Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Sat, 31 Oct 2015 09:35:45 +0100 Subject: [PATCH 01/20] ReplayGain: Fix confusion between ratio <-> peak --- src/util/replaygain.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util/replaygain.h b/src/util/replaygain.h index 09f2c77b24d..bf53055c5dc 100644 --- a/src/util/replaygain.h +++ b/src/util/replaygain.h @@ -47,7 +47,7 @@ class ReplayGain { // The peak amplitude of the track or signal. CSAMPLE getPeak() const { - return m_ratio; + return m_peak; } void setPeak(CSAMPLE peak) { m_peak = peak; From c8e980f0982c3e9100eae01d12ba54f1af1e9548 Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Sat, 31 Oct 2015 09:37:23 +0100 Subject: [PATCH 02/20] ReplayGain: Fix wrong comment --- src/util/replaygain.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util/replaygain.cpp b/src/util/replaygain.cpp index 8ec486eecfa..2b3016802c9 100644 --- a/src/util/replaygain.cpp +++ b/src/util/replaygain.cpp @@ -5,7 +5,7 @@ namespace Mixxx { /*static*/ const double ReplayGain::kRatioUndefined = 0.0; -/*static*/ const double ReplayGain::kRatioMin = 0.0; // lower bound (inclusive) +/*static*/ const double ReplayGain::kRatioMin = 0.0; /*static*/ const double ReplayGain::kRatio0dB = 1.0; namespace { From 1a4a2523346d0032ea7d840b5b80dad5e3d0846a Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Sat, 31 Oct 2015 11:46:21 +0100 Subject: [PATCH 03/20] ReplayGain: Fix parsing of dB gain values --- src/test/replaygaintest.cpp | 18 ++++++++---- src/util/replaygain.cpp | 58 +++++++++++++++++++++++++++++-------- src/util/replaygain.h | 4 +-- 3 files changed, 61 insertions(+), 19 deletions(-) diff --git a/src/test/replaygaintest.cpp b/src/test/replaygaintest.cpp index 92c57416a07..76ba8bb4deb 100644 --- a/src/test/replaygaintest.cpp +++ b/src/test/replaygaintest.cpp @@ -18,7 +18,7 @@ class ReplayGainTest : public testing::Test { virtual void TearDown() { } - double parseGain2Ratio(QString inputValue, bool expectedResult, float expectedValue) { + double parseGain2Ratio(QString inputValue, bool expectedResult, double expectedValue) { //qDebug() << "parseGain2Ratio" << inputValue << expectedResult << expectedValue; bool actualResult; @@ -32,11 +32,15 @@ class ReplayGainTest : public testing::Test { void normalizeRatio(double expectedResult) { const double actualResult = Mixxx::ReplayGain::normalizeRatio(expectedResult); - EXPECT_EQ(expectedResult, actualResult); + if (Mixxx::ReplayGain::isValidRatio(expectedResult)) { + EXPECT_EQ(expectedResult, actualResult); + } else { + EXPECT_EQ(Mixxx::ReplayGain::kRatioUndefined, actualResult); + } } }; -TEST_F(ReplayGainTest, ParseReplayGainDbValidRange) { +TEST_F(ReplayGainTest, ParseGain2RatioValidRange) { for (int replayGainDb = -100; 100 >= replayGainDb; ++replayGainDb) { const QString inputValues[] = { QString("%1 ").arg(replayGainDb), @@ -55,13 +59,17 @@ TEST_F(ReplayGainTest, ParseReplayGainDbValidRange) { } } -TEST_F(ReplayGainTest, ParseReplayGainDbInvalid) { +TEST_F(ReplayGainTest, ParseGain2RatioInvalid) { parseGain2Ratio("", false, Mixxx::ReplayGain::kRatioUndefined); parseGain2Ratio("abcde", false, Mixxx::ReplayGain::kRatioUndefined); parseGain2Ratio("0 dBA", false, Mixxx::ReplayGain::kRatioUndefined); + parseGain2Ratio("--2 dB", false, Mixxx::ReplayGain::kRatioUndefined); + parseGain2Ratio("+-2 dB", false, Mixxx::ReplayGain::kRatioUndefined); + parseGain2Ratio("-+2 dB", false, Mixxx::ReplayGain::kRatioUndefined); + parseGain2Ratio("++2 dB", false, Mixxx::ReplayGain::kRatioUndefined); } -TEST_F(ReplayGainTest, NormalizeReplayGain) { +TEST_F(ReplayGainTest, NormalizeRatio) { normalizeRatio(Mixxx::ReplayGain::kRatioUndefined); normalizeRatio(Mixxx::ReplayGain::kRatioMin); normalizeRatio(-Mixxx::ReplayGain::kRatioMin); diff --git a/src/util/replaygain.cpp b/src/util/replaygain.cpp index 2b3016802c9..a1cf228b78d 100644 --- a/src/util/replaygain.cpp +++ b/src/util/replaygain.cpp @@ -13,28 +13,62 @@ namespace { const QString kGainUnit("dB"); const QString kGainSuffix(" " + kGainUnit); +QString stripLeadingSign(const QString& trimmed, char sign) { + const int signIndex = trimmed.indexOf(sign); + if (0 == signIndex) { + return trimmed.mid(signIndex + 1).trimmed(); + } else { + return trimmed; + } +} + +QString normalizeNumberString(const QString& number, bool* pValid) { + if (pValid) { + *pValid = false; + } + const QString trimmed(number.trimmed()); + QString normalized(stripLeadingSign(trimmed, '+')); + if (normalized == trimmed) { + // no leading '+' sign found + if (pValid) { + *pValid = true; + } + return normalized; + } else { + // stripped leading '+' sign -> no more leading signs '+'/'-' allowed + if ((normalized == stripLeadingSign(normalized, '+')) && + (normalized == stripLeadingSign(normalized, '-'))) { + if (pValid) { + *pValid = true; + } + return normalized; + } + } + // normalization failed + return number; +} + } // anonymous namespace double ReplayGain::parseGain2Ratio(QString dbGain, bool* pValid) { if (pValid) { *pValid = false; } - QString trimmedGain(dbGain.trimmed()); - const int plusIndex = trimmedGain.indexOf('+'); - if (0 == plusIndex) { - // strip leading "+" - trimmedGain = trimmedGain.mid(plusIndex + 1).trimmed(); + bool isValid = false; + QString normalizedGain(normalizeNumberString(dbGain, &isValid)); + if (!isValid) { + return kRatioUndefined; } - const int unitIndex = trimmedGain.lastIndexOf(kGainUnit, -1, Qt::CaseInsensitive); - if ((0 <= unitIndex) && ((trimmedGain.length() - 2) == unitIndex)) { + const int unitIndex = normalizedGain.lastIndexOf(kGainUnit, -1, Qt::CaseInsensitive); + if ((0 <= unitIndex) && ((normalizedGain.length() - 2) == unitIndex)) { // strip trailing unit suffix - trimmedGain = trimmedGain.left(unitIndex).trimmed(); + normalizedGain = normalizedGain.left(unitIndex).trimmed(); } - if (trimmedGain.isEmpty()) { + if (normalizedGain.isEmpty()) { return kRatioUndefined; } - bool isValid = false; - const double replayGainDb = trimmedGain.toDouble(&isValid); + isValid = false; + const double replayGainDb = normalizedGain.toDouble(&isValid); if (isValid) { const double ratio = db2ratio(replayGainDb); DEBUG_ASSERT(kRatioUndefined != ratio); @@ -68,7 +102,7 @@ double ReplayGain::normalizeRatio(double ratio) { DEBUG_ASSERT(normalizedRatio == parseGain2Ratio(formatRatio2Gain(normalizedRatio))); return normalizedRatio; } else { - return ratio; + return kRatioUndefined; } } diff --git a/src/util/replaygain.h b/src/util/replaygain.h index bf53055c5dc..849a97a6642 100644 --- a/src/util/replaygain.h +++ b/src/util/replaygain.h @@ -41,8 +41,8 @@ class ReplayGain { static QString formatRatio2Gain(double ratio); // After normalization formatting and parsing the ratio repeatedly will - // always lead to the same value. This is required to reliably store the - // dB gain as a string in track metadata. + // always result in the same value. This is required to reliably store + // the dB gain as a string in track metadata. static double normalizeRatio(double ratio); // The peak amplitude of the track or signal. From bcb0b07c857f8f00c43df2e25639ef179c8d74ca Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Sat, 31 Oct 2015 09:36:56 +0100 Subject: [PATCH 04/20] ReplayGain: Add constants for peak values --- src/util/replaygain.cpp | 4 ++++ src/util/replaygain.h | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/src/util/replaygain.cpp b/src/util/replaygain.cpp index a1cf228b78d..4b25baebe59 100644 --- a/src/util/replaygain.cpp +++ b/src/util/replaygain.cpp @@ -8,6 +8,10 @@ namespace Mixxx { /*static*/ const double ReplayGain::kRatioMin = 0.0; /*static*/ const double ReplayGain::kRatio0dB = 1.0; +/*static*/ const CSAMPLE ReplayGain::kPeakUndefined = -CSAMPLE_PEAK; +/*static*/ const CSAMPLE ReplayGain::kPeakMin = CSAMPLE_ZERO; +/*static*/ const CSAMPLE ReplayGain::kPeakClip = CSAMPLE_PEAK; + namespace { const QString kGainUnit("dB"); diff --git a/src/util/replaygain.h b/src/util/replaygain.h index 849a97a6642..edd5a5e188e 100644 --- a/src/util/replaygain.h +++ b/src/util/replaygain.h @@ -12,6 +12,10 @@ class ReplayGain { static const double kRatioMin; // lower bound (exclusive) static const double kRatio0dB; + static const CSAMPLE kPeakUndefined; + static const CSAMPLE kPeakMin; // lower bound (inclusive) + static const CSAMPLE kPeakClip; // upper bound (inclusive) without clipping + ReplayGain() : m_ratio(kRatioUndefined) , m_peak(CSAMPLE_PEAK) { From 15229a40d48031756f9d6daa67f30de16d627187 Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Sat, 31 Oct 2015 09:39:07 +0100 Subject: [PATCH 05/20] ReplayGain: Add functions for peak values --- src/util/replaygain.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/util/replaygain.h b/src/util/replaygain.h index edd5a5e188e..423bb4ca884 100644 --- a/src/util/replaygain.h +++ b/src/util/replaygain.h @@ -50,6 +50,12 @@ class ReplayGain { static double normalizeRatio(double ratio); // The peak amplitude of the track or signal. + static bool isValidPeak(CSAMPLE peak) { + return kPeakMin <= peak; + } + bool hasPeak() const { + return isValidPeak(m_peak); + } CSAMPLE getPeak() const { return m_peak; } From 8731104f77a5032254004ea05fc58c9555e41bc2 Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Sat, 31 Oct 2015 11:51:43 +0100 Subject: [PATCH 06/20] ReplayGain: Parse/format/normalize peak values --- src/test/replaygaintest.cpp | 56 +++++++++++++++++++++++++++++++++++++ src/util/replaygain.cpp | 46 ++++++++++++++++++++++++++++++ src/util/replaygain.h | 12 ++++++++ 3 files changed, 114 insertions(+) diff --git a/src/test/replaygaintest.cpp b/src/test/replaygaintest.cpp index 76ba8bb4deb..27e10c99ad5 100644 --- a/src/test/replaygaintest.cpp +++ b/src/test/replaygaintest.cpp @@ -38,6 +38,27 @@ class ReplayGainTest : public testing::Test { EXPECT_EQ(Mixxx::ReplayGain::kRatioUndefined, actualResult); } } + + CSAMPLE parsePeak(QString inputValue, bool expectedResult, CSAMPLE expectedValue) { + //qDebug() << "parsePeak" << inputValue << expectedResult << expectedValue; + + bool actualResult; + const CSAMPLE actualValue = Mixxx::ReplayGain::parsePeak(inputValue, &actualResult); + + EXPECT_EQ(expectedResult, actualResult); + EXPECT_FLOAT_EQ(expectedValue, actualValue); + + return actualResult; + } + + void normalizePeak(CSAMPLE expectedResult) { + const CSAMPLE actualResult = Mixxx::ReplayGain::normalizePeak(expectedResult); + if (Mixxx::ReplayGain::isValidPeak(expectedResult)) { + EXPECT_EQ(expectedResult, actualResult); + } else { + EXPECT_EQ(Mixxx::ReplayGain::kPeakUndefined, actualResult); + } + } }; TEST_F(ReplayGainTest, ParseGain2RatioValidRange) { @@ -77,4 +98,39 @@ TEST_F(ReplayGainTest, NormalizeRatio) { normalizeRatio(-Mixxx::ReplayGain::kRatio0dB); } +TEST_F(ReplayGainTest, ParsePeakValid) { + parsePeak("0", true, Mixxx::ReplayGain::kPeakMin); + parsePeak("+0", true, Mixxx::ReplayGain::kPeakMin); + parsePeak("-0", true, Mixxx::ReplayGain::kPeakMin); + parsePeak("0.0", true, Mixxx::ReplayGain::kPeakMin); + parsePeak("+0.0", true, Mixxx::ReplayGain::kPeakMin); + parsePeak("-0.0", true, Mixxx::ReplayGain::kPeakMin); + parsePeak("1", true, Mixxx::ReplayGain::kPeakClip); + parsePeak("+1", true, Mixxx::ReplayGain::kPeakClip); + parsePeak("1.0", true, Mixxx::ReplayGain::kPeakClip); + parsePeak("+1.0", true, Mixxx::ReplayGain::kPeakClip); + parsePeak(" 0.12345 ", true, 0.12345); + parsePeak(" 1.2345", true, 1.2345); +} + +TEST_F(ReplayGainTest, ParsePeakInvalid) { + parsePeak("", false, Mixxx::ReplayGain::kPeakUndefined); + parsePeak("-1", false, Mixxx::ReplayGain::kPeakUndefined); + parsePeak("-0.12345", false, Mixxx::ReplayGain::kPeakUndefined); + parsePeak("--1.0", false, Mixxx::ReplayGain::kPeakUndefined); + parsePeak("+-1.0", false, Mixxx::ReplayGain::kPeakUndefined); + parsePeak("-+1.0", false, Mixxx::ReplayGain::kPeakUndefined); + parsePeak("++1.0", false, Mixxx::ReplayGain::kPeakUndefined); + parsePeak("+abcde", false, Mixxx::ReplayGain::kPeakUndefined); +} + +TEST_F(ReplayGainTest, NormalizePeak) { + normalizePeak(Mixxx::ReplayGain::kPeakUndefined); + normalizePeak(Mixxx::ReplayGain::kPeakMin); + normalizePeak(-Mixxx::ReplayGain::kPeakMin); + normalizePeak(Mixxx::ReplayGain::kPeakClip); + normalizePeak(-Mixxx::ReplayGain::kPeakClip); + normalizePeak(Mixxx::ReplayGain::kPeakClip + Mixxx::ReplayGain::kPeakClip); +} + } // anonymous namespace diff --git a/src/util/replaygain.cpp b/src/util/replaygain.cpp index 4b25baebe59..fc2a58ec1e7 100644 --- a/src/util/replaygain.cpp +++ b/src/util/replaygain.cpp @@ -110,4 +110,50 @@ double ReplayGain::normalizeRatio(double ratio) { } } +CSAMPLE ReplayGain::parsePeak(QString strPeak, bool* pValid) { + if (pValid) { + *pValid = false; + } + bool isValid = false; + QString normalizedPeak(normalizeNumberString(strPeak, &isValid)); + if (!isValid || normalizedPeak.isEmpty()) { + return kPeakUndefined; + } + isValid = false; + const CSAMPLE peak = normalizedPeak.toDouble(&isValid); + if (isValid) { + if (isValidPeak(peak)) { + if (pValid) { + *pValid = true; + } + return peak; + } else { + qDebug() << "ReplayGain: Invalid peak value:" << strPeak << " -> "<< peak; + } + } else { + qDebug() << "ReplayGain: Failed to parse peak:" << strPeak; + } + return kPeakUndefined; +} + +QString ReplayGain::formatPeak(CSAMPLE peak) { + if (isValidPeak(peak)) { + return QString::number(peak); + } else { + return QString(); + } +} + +CSAMPLE ReplayGain::normalizePeak(CSAMPLE peak) { + if (isValidPeak(peak)) { + const CSAMPLE normalizedPeak = parsePeak(formatPeak(peak)); + // NOTE(uklotzde): Subsequently formatting and parsing the + // normalized value should not alter it anymore! + DEBUG_ASSERT(normalizedPeak == parsePeak(formatPeak(normalizedPeak))); + return normalizedPeak; + } else { + return kPeakUndefined; + } +} + } //namespace Mixxx diff --git a/src/util/replaygain.h b/src/util/replaygain.h index 423bb4ca884..bd19967ad0e 100644 --- a/src/util/replaygain.h +++ b/src/util/replaygain.h @@ -66,6 +66,18 @@ class ReplayGain { m_peak = CSAMPLE_PEAK; } + // Parse and format the peak value metadata according to the ReplayGain + // 1.0/2.0 specification. + // http://wiki.hydrogenaud.io/index.php?title=ReplayGain_1.0_specification + // http://wiki.hydrogenaud.io/index.php?title=ReplayGain_2.0_specification + static CSAMPLE parsePeak(QString strPeak, bool* pValid = 0); + static QString formatPeak(CSAMPLE peak); + + // After normalization formatting and parsing the peak repeatedly will + // always result in the same value. This is required to reliably store + // the peak value as a string in track metadata. + static CSAMPLE normalizePeak(CSAMPLE peak); + private: double m_ratio; CSAMPLE m_peak; From 358af37fa03ebbea77f9a6a2d3a23f9a10e98011 Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Sat, 31 Oct 2015 09:38:26 +0100 Subject: [PATCH 07/20] ReplayGain: Add initializing constructor --- src/util/replaygain.h | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/util/replaygain.h b/src/util/replaygain.h index bd19967ad0e..80b81abf95d 100644 --- a/src/util/replaygain.h +++ b/src/util/replaygain.h @@ -17,8 +17,11 @@ class ReplayGain { static const CSAMPLE kPeakClip; // upper bound (inclusive) without clipping ReplayGain() - : m_ratio(kRatioUndefined) - , m_peak(CSAMPLE_PEAK) { + : ReplayGain(kRatioUndefined, kPeakUndefined) { + } + ReplayGain(double ratio, CSAMPLE peak) + : m_ratio(ratio) + , m_peak(peak) { } static bool isValidRatio(double ratio) { From 9fd2a09e00fbaab148e9dcd4acd0536612ab1ae3 Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Sat, 31 Oct 2015 12:19:05 +0100 Subject: [PATCH 08/20] Track metadata: Complete replay gain support incl. peak amplitude --- src/metadata/trackmetadatataglib.cpp | 80 +++++++++++++++++++++++----- 1 file changed, 66 insertions(+), 14 deletions(-) diff --git a/src/metadata/trackmetadatataglib.cpp b/src/metadata/trackmetadatataglib.cpp index a8232ba607d..aa10958263e 100644 --- a/src/metadata/trackmetadatataglib.cpp +++ b/src/metadata/trackmetadatataglib.cpp @@ -196,26 +196,46 @@ inline QString formatTrackGain(const TrackMetadata& trackMetadata) { return ReplayGain::formatRatio2Gain(trackGainRatio); } -bool parseTrackGain(TrackMetadata* pTrackMetadata, QString dbGain) { +void parseTrackGain( + TrackMetadata* pTrackMetadata, + const QString& dbGain) { DEBUG_ASSERT(pTrackMetadata); - bool trackGainRatioValid = false; - double trackGainRatio = ReplayGain::parseGain2Ratio(dbGain, &trackGainRatioValid); - if (trackGainRatioValid) { + bool isRatioValid = false; + double ratio = ReplayGain::parseGain2Ratio(dbGain, &isRatioValid); + if (isRatioValid) { // Some applications (e.g. Rapid Evolution 3) write a replay gain // of 0 dB even if the replay gain is undefined. To be safe we // ignore this special value and instead prefer to recalculate // the replay gain. - if (trackGainRatio == ReplayGain::kRatio0dB) { + if (ratio == ReplayGain::kRatio0dB) { // special case - qDebug() << "Ignoring possibly undefined gain:" << ReplayGain::formatRatio2Gain(trackGainRatio); - trackGainRatio = ReplayGain::kRatioUndefined; + qDebug() << "Ignoring possibly undefined gain:" << dbGain; + ratio = ReplayGain::kRatioUndefined; } - ReplayGain trackGain(pTrackMetadata->getReplayGain()); - trackGain.setRatio(trackGainRatio); - pTrackMetadata->setReplayGain(trackGain); + ReplayGain replayGain(pTrackMetadata->getReplayGain()); + replayGain.setRatio(ratio); + pTrackMetadata->setReplayGain(replayGain); + } +} + +inline QString formatTrackPeak(const TrackMetadata& trackMetadata) { + const CSAMPLE trackGainPeak(trackMetadata.getReplayGain().getPeak()); + return ReplayGain::formatPeak(trackGainPeak); +} + +void parseTrackPeak( + TrackMetadata* pTrackMetadata, + const QString& strPeak) { + DEBUG_ASSERT(pTrackMetadata); + + bool isPeakValid = false; + const CSAMPLE peak = ReplayGain::parsePeak(strPeak, &isPeakValid); + if (isPeakValid) { + ReplayGain replayGain(pTrackMetadata->getReplayGain()); + replayGain.setPeak(peak); + pTrackMetadata->setReplayGain(replayGain); } - return trackGainRatioValid; } void readAudioProperties(TrackMetadata* pTrackMetadata, @@ -651,12 +671,19 @@ void readTrackMetadataFromID3v2Tag(TrackMetadata* pTrackMetadata, } // Only read track gain (not album gain) - TagLib::ID3v2::UserTextIdentificationFrame* pReplayGainFrame = + TagLib::ID3v2::UserTextIdentificationFrame* pTrackGainFrame = findFirstUserTextIdentificationFrame(tag, "REPLAYGAIN_TRACK_GAIN"); - if (pReplayGainFrame && (2 <= pReplayGainFrame->fieldList().size())) { + if (pTrackGainFrame && (2 <= pTrackGainFrame->fieldList().size())) { // The value is stored in the 2nd field parseTrackGain(pTrackMetadata, - toQString(pReplayGainFrame->fieldList()[1])); + toQString(pTrackGainFrame->fieldList()[1])); + } + TagLib::ID3v2::UserTextIdentificationFrame* pTrackPeakFrame = + findFirstUserTextIdentificationFrame(tag, "REPLAYGAIN_TRACK_PEAK"); + if (pTrackPeakFrame && (2 <= pTrackPeakFrame->fieldList().size())) { + // The value is stored in the 2nd field + parseTrackPeak(pTrackMetadata, + toQString(pTrackPeakFrame->fieldList()[1])); } } @@ -697,6 +724,10 @@ void readTrackMetadataFromAPETag(TrackMetadata* pTrackMetadata, const TagLib::AP parseTrackGain(pTrackMetadata, toQString(tag.itemListMap()["REPLAYGAIN_TRACK_GAIN"])); } + if (tag.itemListMap().contains("REPLAYGAIN_TRACK_PEAK")) { + parseTrackPeak(pTrackMetadata, + toQString(tag.itemListMap()["REPLAYGAIN_TRACK_PEAK"])); + } } void readTrackMetadataFromXiphComment(TrackMetadata* pTrackMetadata, @@ -766,6 +797,10 @@ void readTrackMetadataFromXiphComment(TrackMetadata* pTrackMetadata, parseTrackGain(pTrackMetadata, toQStringFirstNotEmpty(tag.fieldListMap()["REPLAYGAIN_TRACK_GAIN"])); } + if (tag.fieldListMap().contains("REPLAYGAIN_TRACK_PEAK")) { + parseTrackPeak(pTrackMetadata, + toQStringFirstNotEmpty(tag.fieldListMap()["REPLAYGAIN_TRACK_PEAK"])); + } /* * Reading key code information @@ -843,6 +878,11 @@ void readTrackMetadataFromMP4Tag(TrackMetadata* pTrackMetadata, const TagLib::MP parseTrackGain(pTrackMetadata, toQStringFirstNotEmpty(getItemListMap(tag)["----:com.apple.iTunes:replaygain_track_gain"])); } + if (getItemListMap(tag).contains( + "----:com.apple.iTunes:replaygain_track_peak")) { + parseTrackPeak(pTrackMetadata, + toQStringFirstNotEmpty(getItemListMap(tag)["----:com.apple.iTunes:replaygain_track_peak"])); + } // Read musical key (conforms to Rapid Evolution) if (getItemListMap(tag).contains("----:com.apple.iTunes:KEY")) { @@ -916,6 +956,11 @@ bool writeTrackMetadataIntoID3v2Tag(TagLib::ID3v2::Tag* pTag, formatTrackGain(trackMetadata), "REPLAYGAIN_TRACK_GAIN", true); + writeID3v2UserTextIdentificationFrame( + pTag, + formatTrackPeak(trackMetadata), + "REPLAYGAIN_TRACK_PEAK", + true); return true; } @@ -939,6 +984,8 @@ bool writeTrackMetadataIntoAPETag(TagLib::APE::Tag* pTag, const TrackMetadata& t toTagLibString(TrackMetadata::formatBpm(trackMetadata.getBpm())), true); pTag->addValue("REPLAYGAIN_TRACK_GAIN", toTagLibString(formatTrackGain(trackMetadata)), true); + pTag->addValue("REPLAYGAIN_TRACK_PEAK", + toTagLibString(formatTrackPeak(trackMetadata)), true); return true; } @@ -983,6 +1030,9 @@ bool writeTrackMetadataIntoXiphComment(TagLib::Ogg::XiphComment* pTag, pTag->removeField("REPLAYGAIN_TRACK_GAIN"); pTag->addField("REPLAYGAIN_TRACK_GAIN", toTagLibString(formatTrackGain(trackMetadata))); + pTag->removeField("REPLAYGAIN_TRACK_PEAK"); + pTag->addField("REPLAYGAIN_TRACK_PEAK", + toTagLibString(formatTrackPeak(trackMetadata))); return true; } @@ -1007,6 +1057,8 @@ bool writeTrackMetadataIntoMP4Tag(TagLib::MP4::Tag* pTag, const TrackMetadata& t TrackMetadata::formatBpm(trackMetadata.getBpm())); writeMP4Atom(pTag, "----:com.apple.iTunes:replaygain_track_gain", formatTrackGain(trackMetadata)); + writeMP4Atom(pTag, "----:com.apple.iTunes:replaygain_track_peak", + formatTrackPeak(trackMetadata)); writeMP4Atom(pTag, "----:com.apple.iTunes:initialkey", trackMetadata.getKey()); writeMP4Atom(pTag, "----:com.apple.iTunes:KEY", From 00fab0aeb39e58ee00ea6716a0db3fb355cda9bb Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Sat, 31 Oct 2015 14:10:39 +0100 Subject: [PATCH 09/20] DB schema v25: Complete replay gain support incl. peak amplitude --- res/schema.xml | 10 ++++++++++ src/analyserrg.cpp | 11 ++++------- src/basetrackplayer.cpp | 12 ++++++------ src/basetrackplayer.h | 2 +- src/library/dao/trackdao.cpp | 29 +++++++++++++++++++++-------- src/library/trackcollection.cpp | 2 +- src/trackinfoobject.cpp | 22 ++++++++-------------- src/trackinfoobject.h | 9 +++++---- src/util/replaygain.h | 2 ++ 9 files changed, 58 insertions(+), 41 deletions(-) diff --git a/res/schema.xml b/res/schema.xml index 7d26095f722..abda817a68a 100644 --- a/res/schema.xml +++ b/res/schema.xml @@ -395,4 +395,14 @@ METADATA ALTER TABLE library ADD COLUMN coverart_hash INTEGER DEFAULT 0; + + + Add full replay gain support including peak amplitude. The default + value for the peak amplitude is "undefined", represented by any + negative value. The internal constant for "undefined" is -1.0. + + + ALTER TABLE library ADD COLUMN replaygain_peak REAL DEFAULT -1.0; + + diff --git a/src/analyserrg.cpp b/src/analyserrg.cpp index 5be15c55f80..5af96d0392b 100644 --- a/src/analyserrg.cpp +++ b/src/analyserrg.cpp @@ -31,8 +31,7 @@ bool AnalyserGain::initialise(TrackPointer tio, int sampleRate, int totalSamples bool AnalyserGain::loadStored(TrackPointer tio) const { bool bAnalyserEnabled = (bool)m_pConfigReplayGain->getValueString(ConfigKey("[ReplayGain]","ReplayGainAnalyserEnabled")).toInt(); - float fReplayGain = tio->getReplayGain(); - if (fReplayGain != 0 || !bAnalyserEnabled) { + if (tio->getReplayGain().hasRatio() || !bAnalyserEnabled) { return true; } return false; @@ -76,11 +75,9 @@ void AnalyserGain::finalise(TrackPointer tio) { return; } - float fReplayGain_Result = db2ratio(ReplayGainOutput); + Mixxx::ReplayGain replayGain(tio->getReplayGain()); + replayGain.setRatio(db2ratio(ReplayGainOutput)); + tio->setReplayGain(replayGain); - //qDebug() << "ReplayGain result is" << ReplayGainOutput << "pow:" << fReplayGain_Result; - //qDebug()<<"ReplayGain outputs "<< ReplayGainOutput << "db for track "<< tio->getLocation(); - tio->setReplayGain(fReplayGain_Result); - //if(fReplayGain_Result) qDebug() << "ReplayGain Analyser found a ReplayGain value of "<< 20*log10(fReplayGain_Result) << "dB for track " << (tio->getLocation()); m_bStepControl=false; } diff --git a/src/basetrackplayer.cpp b/src/basetrackplayer.cpp index 78283c02d25..4c9e990b3a4 100644 --- a/src/basetrackplayer.cpp +++ b/src/basetrackplayer.cpp @@ -168,8 +168,8 @@ void BaseTrackPlayerImpl::slotLoadTrack(TrackPointer track, bool bPlay) { m_pKey, SLOT(slotSet(double))); // Listen for updates to the file's Replay Gain - connect(m_pLoadedTrack.data(), SIGNAL(ReplayGainUpdated(double)), - this, SLOT(slotSetReplayGain(double))); + connect(m_pLoadedTrack.data(), SIGNAL(ReplayGainUpdated(Mixxx::ReplayGain)), + this, SLOT(slotSetReplayGain(Mixxx::ReplayGain))); } // Request a new track from the reader @@ -231,7 +231,7 @@ void BaseTrackPlayerImpl::slotFinishLoading(TrackPointer pTrackInfoObject) m_pDuration->set(m_pLoadedTrack->getDuration()); m_pBPM->slotSet(m_pLoadedTrack->getBpm()); m_pKey->slotSet(m_pLoadedTrack->getKey()); - m_pReplayGain->set(m_pLoadedTrack->getReplayGain()); + m_pReplayGain->set(m_pLoadedTrack->getReplayGain().getRatio()); // Update the PlayerInfo class that is used in EngineShoutcast to replace // the metadata of a stream @@ -298,11 +298,11 @@ TrackPointer BaseTrackPlayerImpl::getLoadedTrack() const { return m_pLoadedTrack; } -void BaseTrackPlayerImpl::slotSetReplayGain(double replayGain) { +void BaseTrackPlayerImpl::slotSetReplayGain(Mixxx::ReplayGain replayGain) { // Do not change replay gain when track is playing because // this may lead to an unexpected volume change if (m_pPlay->get() == 0.0) { - m_pReplayGain->set(replayGain); + m_pReplayGain->set(replayGain.getRatio()); } else { m_replaygainPending = true; } @@ -310,7 +310,7 @@ void BaseTrackPlayerImpl::slotSetReplayGain(double replayGain) { void BaseTrackPlayerImpl::slotPlayToggled(double v) { if (!v && m_replaygainPending) { - m_pReplayGain->set(m_pLoadedTrack->getReplayGain()); + m_pReplayGain->set(m_pLoadedTrack->getReplayGain().getRatio()); m_replaygainPending = false; } } diff --git a/src/basetrackplayer.h b/src/basetrackplayer.h index 406a6c0adee..36398a8e1d6 100644 --- a/src/basetrackplayer.h +++ b/src/basetrackplayer.h @@ -68,7 +68,7 @@ class BaseTrackPlayerImpl : public BaseTrackPlayer { void slotFinishLoading(TrackPointer pTrackInfoObject); void slotLoadFailed(TrackPointer pTrackInfoObject, QString reason); void slotUnloadTrack(TrackPointer track); - void slotSetReplayGain(double replayGain); + void slotSetReplayGain(Mixxx::ReplayGain replayGain); void slotPlayToggled(double); private: diff --git a/src/library/dao/trackdao.cpp b/src/library/dao/trackdao.cpp index cc48b695d35..a88f6f3720e 100644 --- a/src/library/dao/trackdao.cpp +++ b/src/library/dao/trackdao.cpp @@ -402,7 +402,8 @@ void TrackDAO::bindTrackToLibraryInsert(TrackInfoObject* pTrack, int trackLocati m_pQueryLibraryInsert->bindValue(":samplerate", pTrack->getSampleRate()); m_pQueryLibraryInsert->bindValue(":cuepoint", pTrack->getCuePoint()); m_pQueryLibraryInsert->bindValue(":bpm_lock", pTrack->hasBpmLock()? 1 : 0); - m_pQueryLibraryInsert->bindValue(":replaygain", pTrack->getReplayGain()); + m_pQueryLibraryInsert->bindValue(":replaygain", pTrack->getReplayGain().getRatio()); + m_pQueryLibraryInsert->bindValue(":replaygain_peak", pTrack->getReplayGain().getPeak()); // We no longer store the wavesummary in the library table. m_pQueryLibraryInsert->bindValue(":wavesummaryhex", QVariant(QVariant::ByteArray)); @@ -490,7 +491,7 @@ void TrackDAO::addTracksPrepare() { m_pQueryLibraryInsert->prepare("INSERT INTO library " "(artist, title, album, album_artist, year, genre, tracknumber, composer, " "grouping, filetype, location, comment, url, duration, rating, key, key_id, " - "bitrate, samplerate, cuepoint, bpm, replaygain, wavesummaryhex, " + "bitrate, samplerate, cuepoint, bpm, replaygain, replaygain_peak, wavesummaryhex, " "timesplayed, channels, mixxx_deleted, header_parsed, " "beats_version, beats_sub_version, beats, bpm_lock, " "keys_version, keys_sub_version, keys, " @@ -498,7 +499,7 @@ void TrackDAO::addTracksPrepare() { "VALUES (" ":artist, :title, :album, :album_artist, :year, :genre, :tracknumber, :composer, :grouping, " ":filetype, :location, :comment, :url, :duration, :rating, :key, :key_id, " - ":bitrate, :samplerate, :cuepoint, :bpm, :replaygain, :wavesummaryhex, " + ":bitrate, :samplerate, :cuepoint, :bpm, :replaygain, :replaygain_peak, :wavesummaryhex, " ":timesplayed, :channels, :mixxx_deleted, :header_parsed, " ":beats_version, :beats_sub_version, :beats, :bpm_lock, " ":keys_version, :keys_sub_version, :keys, " @@ -1062,9 +1063,19 @@ bool setTrackCuePoint(const QSqlRecord& record, const int column, return false; } -bool setTrackReplayGain(const QSqlRecord& record, const int column, +bool setTrackReplayGainRatio(const QSqlRecord& record, const int column, TrackPointer pTrack) { - pTrack->setReplayGain(record.value(column).toDouble()); + Mixxx::ReplayGain replayGain(pTrack->getReplayGain()); + replayGain.setRatio(record.value(column).toDouble()); + pTrack->setReplayGain(replayGain); + return false; +} + +bool setTrackReplayGainPeak(const QSqlRecord& record, const int column, + TrackPointer pTrack) { + Mixxx::ReplayGain replayGain(pTrack->getReplayGain()); + replayGain.setPeak(record.value(column).toDouble()); + pTrack->setReplayGain(replayGain); return false; } @@ -1194,7 +1205,8 @@ TrackPointer TrackDAO::getTrackFromDB(TrackId trackId) const { { "bitrate", setTrackBitrate }, { "samplerate", setTrackSampleRate }, { "cuepoint", setTrackCuePoint }, - { "replaygain", setTrackReplayGain }, + { "replaygain", setTrackReplayGainRatio }, + { "replaygain_peak", setTrackReplayGainPeak }, { "channels", setTrackChannels }, { "timesplayed", setTrackTimesPlayed }, { "played", setTrackPlayed }, @@ -1428,7 +1440,7 @@ void TrackDAO::updateTrack(TrackInfoObject* pTrack) { "duration=:duration, rating=:rating, " "key=:key, key_id=:key_id, " "bitrate=:bitrate, samplerate=:samplerate, cuepoint=:cuepoint, " - "bpm=:bpm, replaygain=:replaygain, " + "bpm=:bpm, replaygain=:replaygain, replaygain_peak=:replaygain_peak, " "timesplayed=:timesplayed, played=:played, " "channels=:channels, header_parsed=:header_parsed, " "beats_version=:beats_version, beats_sub_version=:beats_sub_version, beats=:beats, " @@ -1454,7 +1466,8 @@ void TrackDAO::updateTrack(TrackInfoObject* pTrack) { query.bindValue(":samplerate", pTrack->getSampleRate()); query.bindValue(":cuepoint", pTrack->getCuePoint()); - query.bindValue(":replaygain", pTrack->getReplayGain()); + query.bindValue(":replaygain", pTrack->getReplayGain().getRatio()); + query.bindValue(":replaygain_peak", pTrack->getReplayGain().getPeak()); query.bindValue(":rating", pTrack->getRating()); query.bindValue(":timesplayed", pTrack->getTimesPlayed()); query.bindValue(":played", pTrack->getPlayed() ? 1 : 0); diff --git a/src/library/trackcollection.cpp b/src/library/trackcollection.cpp index 9a368573f81..f8563bc4f1a 100644 --- a/src/library/trackcollection.cpp +++ b/src/library/trackcollection.cpp @@ -14,7 +14,7 @@ #include "util/assert.h" // static -const int TrackCollection::kRequiredSchemaVersion = 24; +const int TrackCollection::kRequiredSchemaVersion = 25; TrackCollection::TrackCollection(ConfigObject* pConfig) : m_pConfig(pConfig), diff --git a/src/trackinfoobject.cpp b/src/trackinfoobject.cpp index 96c71207393..2e1b084d85c 100644 --- a/src/trackinfoobject.cpp +++ b/src/trackinfoobject.cpp @@ -39,7 +39,6 @@ TrackInfoObject::TrackInfoObject(const QFileInfo& fileInfo, m_iDuration = 0; m_iBitrate = 0; m_iTimesPlayed = 0; - m_fReplayGain = 0.; m_iSampleRate = 0; m_iChannels = 0; m_fCuePoint = 0.0f; @@ -112,10 +111,7 @@ void TrackInfoObject::setMetadata(const Mixxx::TrackMetadata& trackMetadata) { setSampleRate(trackMetadata.getSampleRate()); setDuration(trackMetadata.getDuration()); setBitrate(trackMetadata.getBitrate()); - - if (trackMetadata.getReplayGain().hasRatio()) { - setReplayGain(trackMetadata.getReplayGain().getRatio()); - } + setReplayGain(trackMetadata.getReplayGain()); // Need to set BPM after sample rate since beat grid creation depends on // knowing the sample rate. Bug #1020438. @@ -147,9 +143,7 @@ void TrackInfoObject::getMetadata(Mixxx::TrackMetadata* pTrackMetadata) { pTrackMetadata->setSampleRate(getSampleRate()); pTrackMetadata->setDuration(getDuration()); pTrackMetadata->setBitrate(getBitrate()); - Mixxx::ReplayGain trackGain(pTrackMetadata->getReplayGain()); - trackGain.setRatio(getReplayGain()); - pTrackMetadata->setReplayGain(trackGain); + pTrackMetadata->setReplayGain(getReplayGain()); pTrackMetadata->setBpm(getBpm()); pTrackMetadata->setKey(getKeyText()); } @@ -291,20 +285,20 @@ bool TrackInfoObject::exists() const { return QFile::exists(m_fileInfo.absoluteFilePath()); } -float TrackInfoObject::getReplayGain() const { +Mixxx::ReplayGain TrackInfoObject::getReplayGain() const { QMutexLocker lock(&m_qMutex); - return m_fReplayGain; + return m_replayGain; } -void TrackInfoObject::setReplayGain(float f) { +void TrackInfoObject::setReplayGain(const Mixxx::ReplayGain& replayGain) { QMutexLocker lock(&m_qMutex); //qDebug() << "Reported ReplayGain value: " << m_fReplayGain; - if (m_fReplayGain != f) { - m_fReplayGain = f; + if (m_replayGain != replayGain) { + m_replayGain = replayGain; setDirty(true); } lock.unlock(); - emit(ReplayGainUpdated(f)); + emit(ReplayGainUpdated(replayGain)); } double TrackInfoObject::getBpm() const { diff --git a/src/trackinfoobject.h b/src/trackinfoobject.h index ab81a47927e..d164c5b70e7 100644 --- a/src/trackinfoobject.h +++ b/src/trackinfoobject.h @@ -17,6 +17,7 @@ #include "track/beats.h" #include "track/keys.h" #include "track/trackid.h" +#include "util/replaygain.h" #include "util/sandbox.h" #include "waveform/waveform.h" @@ -99,9 +100,9 @@ class TrackInfoObject : public QObject { bool exists() const; // Returns ReplayGain - float getReplayGain() const; + Mixxx::ReplayGain getReplayGain() const; // Set ReplayGain - void setReplayGain(float); + void setReplayGain(const Mixxx::ReplayGain&); // Returns BPM double getBpm() const; // Set BPM @@ -269,7 +270,7 @@ class TrackInfoObject : public QObject { void beatsUpdated(); void keyUpdated(double key); void keysUpdated(); - void ReplayGainUpdated(double replaygain); + void ReplayGainUpdated(Mixxx::ReplayGain replayGain); void cuesUpdated(); void changed(TrackInfoObject* pTrack); void dirty(TrackInfoObject* pTrack); @@ -346,7 +347,7 @@ class TrackInfoObject : public QObject { // Number of times the track has been played int m_iTimesPlayed; // Replay Gain volume - float m_fReplayGain; + Mixxx::ReplayGain m_replayGain; // Has this track been played this sessions? bool m_bPlayed; // True if header was parsed diff --git a/src/util/replaygain.h b/src/util/replaygain.h index 80b81abf95d..71d8abd6505 100644 --- a/src/util/replaygain.h +++ b/src/util/replaygain.h @@ -98,4 +98,6 @@ bool operator!=(const ReplayGain& lhs, const ReplayGain& rhs) { } +Q_DECLARE_METATYPE(Mixxx::ReplayGain) + #endif // MIXXX_REPLAYGAIN_H From f5f5d4e68f87dceac5009612bb7b8abb383651a4 Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Tue, 3 Nov 2015 19:39:37 +0100 Subject: [PATCH 10/20] ReplayGain: Improve documentation (review comments) --- src/util/replaygain.h | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/src/util/replaygain.h b/src/util/replaygain.h index 71d8abd6505..57adb37b0d2 100644 --- a/src/util/replaygain.h +++ b/src/util/replaygain.h @@ -5,7 +5,23 @@ namespace Mixxx { -// DTO for replay gain. Must not be subclassed (no virtual destructor)! +// DTO for storing replay gain information. This class cannot be subclassed, +// because the destructor is not virtual! +// +// Parsing & Formatting +// -------------------- +// This class includes functions for formatting and parsing replay gain +// metadata according to the ReplayGain 1.0/2.0 specification: +// http://wiki.hydrogenaud.io/index.php?title=ReplayGain_1.0_specification +// http://wiki.hydrogenaud.io/index.php?title=ReplayGain_2.0_specification +// +// Normalization +// ------------- +// Formatting a floating point value as a string and parsing it later +// might cause rounding errors. In order to avoid "jittering" caused +// by subsequently formatting and parsing a floating point value the +// ratio and peak values need to be normalized before writing them +// as a string into file tags. class ReplayGain { public: static const double kRatioUndefined; @@ -40,16 +56,9 @@ class ReplayGain { m_ratio = kRatioUndefined; } - // Parse and format replay gain metadata according to the ReplayGain - // 1.0/2.0 specification. - // http://wiki.hydrogenaud.io/index.php?title=ReplayGain_1.0_specification - // http://wiki.hydrogenaud.io/index.php?title=ReplayGain_2.0_specification static double parseGain2Ratio(QString dBGain, bool* pValid = 0); static QString formatRatio2Gain(double ratio); - // After normalization formatting and parsing the ratio repeatedly will - // always result in the same value. This is required to reliably store - // the dB gain as a string in track metadata. static double normalizeRatio(double ratio); // The peak amplitude of the track or signal. @@ -69,16 +78,9 @@ class ReplayGain { m_peak = CSAMPLE_PEAK; } - // Parse and format the peak value metadata according to the ReplayGain - // 1.0/2.0 specification. - // http://wiki.hydrogenaud.io/index.php?title=ReplayGain_1.0_specification - // http://wiki.hydrogenaud.io/index.php?title=ReplayGain_2.0_specification static CSAMPLE parsePeak(QString strPeak, bool* pValid = 0); static QString formatPeak(CSAMPLE peak); - // After normalization formatting and parsing the peak repeatedly will - // always result in the same value. This is required to reliably store - // the peak value as a string in track metadata. static CSAMPLE normalizePeak(CSAMPLE peak); private: From 77b8f18d56d56f1b500ed6b62e8538fde19ccca1 Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Tue, 3 Nov 2015 19:39:59 +0100 Subject: [PATCH 11/20] ReplayGain: Prevent subclassing --- src/util/replaygain.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util/replaygain.h b/src/util/replaygain.h index 57adb37b0d2..2f40846eeaf 100644 --- a/src/util/replaygain.h +++ b/src/util/replaygain.h @@ -22,7 +22,7 @@ namespace Mixxx { // by subsequently formatting and parsing a floating point value the // ratio and peak values need to be normalized before writing them // as a string into file tags. -class ReplayGain { +class ReplayGain final { public: static const double kRatioUndefined; static const double kRatioMin; // lower bound (exclusive) From bc0857021e4429477a260acc50eb003fa3167665 Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Tue, 3 Nov 2015 19:41:09 +0100 Subject: [PATCH 12/20] Transform const into constexpr for static assertions --- src/util/types.h | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/util/types.h b/src/util/types.h index 3a27c1e6f17..d4d3ae0b12d 100644 --- a/src/util/types.h +++ b/src/util/types.h @@ -16,9 +16,9 @@ typedef std::ptrdiff_t SINT; // 16-bit integer sample data within the asymmetric // range [SHRT_MIN, SHRT_MAX]. typedef short int SAMPLE; -const SAMPLE SAMPLE_ZERO = 0; -const SAMPLE SAMPLE_MIN = SHRT_MIN; -const SAMPLE SAMPLE_MAX = SHRT_MAX; +constexpr SAMPLE SAMPLE_ZERO = 0; +constexpr SAMPLE SAMPLE_MIN = SHRT_MIN; +constexpr SAMPLE SAMPLE_MAX = SHRT_MAX; // Limits the range of a SAMPLE value to [SAMPLE_MIN, SAMPLE_MAX]. inline @@ -38,9 +38,9 @@ SAMPLE SAMPLE_clampSymmetric(SAMPLE in) { // emphasize the symmetric value range of CSAMPLE // data! typedef float CSAMPLE; -const CSAMPLE CSAMPLE_ZERO = 0.0f; -const CSAMPLE CSAMPLE_ONE = 1.0f; -const CSAMPLE CSAMPLE_PEAK = CSAMPLE_ONE; +constexpr CSAMPLE CSAMPLE_ZERO = 0.0f; +constexpr CSAMPLE CSAMPLE_ONE = 1.0f; +constexpr CSAMPLE CSAMPLE_PEAK = CSAMPLE_ONE; // Limits the range of a CSAMPLE value to [-CSAMPLE_PEAK, CSAMPLE_PEAK]. inline @@ -52,10 +52,10 @@ CSAMPLE CSAMPLE_clamp(CSAMPLE in) { // data in the range [0.0, 1.0]. Same data type as // CSAMPLE to avoid type conversions in calculations. typedef CSAMPLE CSAMPLE_GAIN; -const float CSAMPLE_GAIN_ZERO = CSAMPLE_ZERO; -const float CSAMPLE_GAIN_ONE = CSAMPLE_ONE; -const float CSAMPLE_GAIN_MIN = CSAMPLE_GAIN_ZERO; -const float CSAMPLE_GAIN_MAX = CSAMPLE_GAIN_ONE; +constexpr float CSAMPLE_GAIN_ZERO = CSAMPLE_ZERO; +constexpr float CSAMPLE_GAIN_ONE = CSAMPLE_ONE; +constexpr float CSAMPLE_GAIN_MIN = CSAMPLE_GAIN_ZERO; +constexpr float CSAMPLE_GAIN_MAX = CSAMPLE_GAIN_ONE; // Limits the range of a CSAMPLE_GAIN value to [CSAMPLE_GAIN_MIN, CSAMPLE_GAIN_MAX]. inline From d9f4e35b595882993d474b78878ea8cb5ce0ebbc Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Tue, 3 Nov 2015 19:40:20 +0100 Subject: [PATCH 13/20] ReplayGain: Add static assertion for peak amplitude (review comments) --- src/util/replaygain.cpp | 12 ++++++------ src/util/replaygain.h | 22 +++++++++++++--------- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/src/util/replaygain.cpp b/src/util/replaygain.cpp index fc2a58ec1e7..05ad4d1fe27 100644 --- a/src/util/replaygain.cpp +++ b/src/util/replaygain.cpp @@ -4,13 +4,13 @@ namespace Mixxx { -/*static*/ const double ReplayGain::kRatioUndefined = 0.0; -/*static*/ const double ReplayGain::kRatioMin = 0.0; -/*static*/ const double ReplayGain::kRatio0dB = 1.0; +/*static*/ constexpr double ReplayGain::kRatioUndefined; +/*static*/ constexpr double ReplayGain::kRatioMin; +/*static*/ constexpr double ReplayGain::kRatio0dB; -/*static*/ const CSAMPLE ReplayGain::kPeakUndefined = -CSAMPLE_PEAK; -/*static*/ const CSAMPLE ReplayGain::kPeakMin = CSAMPLE_ZERO; -/*static*/ const CSAMPLE ReplayGain::kPeakClip = CSAMPLE_PEAK; +/*static*/ constexpr CSAMPLE ReplayGain::kPeakUndefined; +/*static*/ constexpr CSAMPLE ReplayGain::kPeakMin; +/*static*/ constexpr CSAMPLE ReplayGain::kPeakClip; namespace { diff --git a/src/util/replaygain.h b/src/util/replaygain.h index 2f40846eeaf..2ea9daf2ea6 100644 --- a/src/util/replaygain.h +++ b/src/util/replaygain.h @@ -5,8 +5,7 @@ namespace Mixxx { -// DTO for storing replay gain information. This class cannot be subclassed, -// because the destructor is not virtual! +// DTO for storing replay gain information. // // Parsing & Formatting // -------------------- @@ -24,13 +23,18 @@ namespace Mixxx { // as a string into file tags. class ReplayGain final { public: - static const double kRatioUndefined; - static const double kRatioMin; // lower bound (exclusive) - static const double kRatio0dB; - - static const CSAMPLE kPeakUndefined; - static const CSAMPLE kPeakMin; // lower bound (inclusive) - static const CSAMPLE kPeakClip; // upper bound (inclusive) without clipping + static constexpr double kRatioUndefined = 0.0; + static constexpr double kRatioMin = 0.0; // lower bound (exclusive) + static constexpr double kRatio0dB = 1.0; + + static constexpr CSAMPLE kPeakUndefined = -CSAMPLE_PEAK; + static constexpr CSAMPLE kPeakMin = CSAMPLE_ZERO; // lower bound (inclusive) + static constexpr CSAMPLE kPeakClip = CSAMPLE_PEAK; // upper bound (inclusive) represents digital full scale without clipping + + static_assert(ReplayGain::kPeakClip == 1.0, + "http://wiki.hydrogenaud.io/index.php?title=ReplayGain_2.0_specification#Peak_amplitude: " + "The maximum peak amplitude value is stored as a floating number, " + "where 1.0 represents digital full scale"); ReplayGain() : ReplayGain(kRatioUndefined, kPeakUndefined) { From c8511d3d6988fb96041dc9ae83bae7d1dd748298 Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Tue, 3 Nov 2015 19:40:33 +0100 Subject: [PATCH 14/20] ReplayGain: Rename functions for parsing and formatting (review comments) --- src/metadata/trackmetadatataglib.cpp | 8 +-- src/sources/soundsourceopus.cpp | 2 +- src/test/replaygaintest.cpp | 78 ++++++++++++++-------------- src/util/replaygain.cpp | 16 +++--- src/util/replaygain.h | 12 +++-- 5 files changed, 60 insertions(+), 56 deletions(-) diff --git a/src/metadata/trackmetadatataglib.cpp b/src/metadata/trackmetadatataglib.cpp index aa10958263e..3713d13277d 100644 --- a/src/metadata/trackmetadatataglib.cpp +++ b/src/metadata/trackmetadatataglib.cpp @@ -193,7 +193,7 @@ inline bool parseBpm(TrackMetadata* pTrackMetadata, QString sBpm) { inline QString formatTrackGain(const TrackMetadata& trackMetadata) { const double trackGainRatio(trackMetadata.getReplayGain().getRatio()); - return ReplayGain::formatRatio2Gain(trackGainRatio); + return ReplayGain::ratioToString(trackGainRatio); } void parseTrackGain( @@ -202,7 +202,7 @@ void parseTrackGain( DEBUG_ASSERT(pTrackMetadata); bool isRatioValid = false; - double ratio = ReplayGain::parseGain2Ratio(dbGain, &isRatioValid); + double ratio = ReplayGain::ratioFromString(dbGain, &isRatioValid); if (isRatioValid) { // Some applications (e.g. Rapid Evolution 3) write a replay gain // of 0 dB even if the replay gain is undefined. To be safe we @@ -221,7 +221,7 @@ void parseTrackGain( inline QString formatTrackPeak(const TrackMetadata& trackMetadata) { const CSAMPLE trackGainPeak(trackMetadata.getReplayGain().getPeak()); - return ReplayGain::formatPeak(trackGainPeak); + return ReplayGain::peakToString(trackGainPeak); } void parseTrackPeak( @@ -230,7 +230,7 @@ void parseTrackPeak( DEBUG_ASSERT(pTrackMetadata); bool isPeakValid = false; - const CSAMPLE peak = ReplayGain::parsePeak(strPeak, &isPeakValid); + const CSAMPLE peak = ReplayGain::peakFromString(strPeak, &isPeakValid); if (isPeakValid) { ReplayGain replayGain(pTrackMetadata->getReplayGain()); replayGain.setPeak(peak); diff --git a/src/sources/soundsourceopus.cpp b/src/sources/soundsourceopus.cpp index 192fd646b23..afb728f1982 100644 --- a/src/sources/soundsourceopus.cpp +++ b/src/sources/soundsourceopus.cpp @@ -117,7 +117,7 @@ Result SoundSourceOpus::parseTrackMetadataAndCoverArt( pTrackMetadata->setTitle(l_SPayload); } else if (!l_STag.compare("REPLAYGAIN_TRACK_GAIN")) { bool trackGainRatioValid = false; - double trackGainRatio = ReplayGain::parseGain2Ratio(l_SPayload, &trackGainRatioValid); + double trackGainRatio = ReplayGain::ratioFromString(l_SPayload, &trackGainRatioValid); if (trackGainRatioValid) { ReplayGain trackGain(pTrackMetadata->getReplayGain()); trackGain.setRatio(trackGainRatio); diff --git a/src/test/replaygaintest.cpp b/src/test/replaygaintest.cpp index 27e10c99ad5..81ff73aeb46 100644 --- a/src/test/replaygaintest.cpp +++ b/src/test/replaygaintest.cpp @@ -18,11 +18,11 @@ class ReplayGainTest : public testing::Test { virtual void TearDown() { } - double parseGain2Ratio(QString inputValue, bool expectedResult, double expectedValue) { - //qDebug() << "parseGain2Ratio" << inputValue << expectedResult << expectedValue; + double ratioFromString(QString inputValue, bool expectedResult, double expectedValue) { + //qDebug() << "ratioFromString" << inputValue << expectedResult << expectedValue; bool actualResult; - const double actualValue = Mixxx::ReplayGain::parseGain2Ratio(inputValue, &actualResult); + const double actualValue = Mixxx::ReplayGain::ratioFromString(inputValue, &actualResult); EXPECT_EQ(expectedResult, actualResult); EXPECT_FLOAT_EQ(expectedValue, actualValue); @@ -39,11 +39,11 @@ class ReplayGainTest : public testing::Test { } } - CSAMPLE parsePeak(QString inputValue, bool expectedResult, CSAMPLE expectedValue) { - //qDebug() << "parsePeak" << inputValue << expectedResult << expectedValue; + CSAMPLE peakFromString(QString inputValue, bool expectedResult, CSAMPLE expectedValue) { + //qDebug() << "peakFromString" << inputValue << expectedResult << expectedValue; bool actualResult; - const CSAMPLE actualValue = Mixxx::ReplayGain::parsePeak(inputValue, &actualResult); + const CSAMPLE actualValue = Mixxx::ReplayGain::peakFromString(inputValue, &actualResult); EXPECT_EQ(expectedResult, actualResult); EXPECT_FLOAT_EQ(expectedValue, actualValue); @@ -61,7 +61,7 @@ class ReplayGainTest : public testing::Test { } }; -TEST_F(ReplayGainTest, ParseGain2RatioValidRange) { +TEST_F(ReplayGainTest, RatioFromStringValidRange) { for (int replayGainDb = -100; 100 >= replayGainDb; ++replayGainDb) { const QString inputValues[] = { QString("%1 ").arg(replayGainDb), @@ -72,22 +72,22 @@ TEST_F(ReplayGainTest, ParseGain2RatioValidRange) { float expectedValue; expectedValue = db2ratio(double(replayGainDb)); for (size_t i = 0; i < sizeof(inputValues) / sizeof(inputValues[0]); ++i) { - parseGain2Ratio(inputValues[i], true, expectedValue); + ratioFromString(inputValues[i], true, expectedValue); if (0 <= replayGainDb) { - parseGain2Ratio(QString(" + ") + inputValues[i], true, expectedValue); + ratioFromString(QString(" + ") + inputValues[i], true, expectedValue); } } } } -TEST_F(ReplayGainTest, ParseGain2RatioInvalid) { - parseGain2Ratio("", false, Mixxx::ReplayGain::kRatioUndefined); - parseGain2Ratio("abcde", false, Mixxx::ReplayGain::kRatioUndefined); - parseGain2Ratio("0 dBA", false, Mixxx::ReplayGain::kRatioUndefined); - parseGain2Ratio("--2 dB", false, Mixxx::ReplayGain::kRatioUndefined); - parseGain2Ratio("+-2 dB", false, Mixxx::ReplayGain::kRatioUndefined); - parseGain2Ratio("-+2 dB", false, Mixxx::ReplayGain::kRatioUndefined); - parseGain2Ratio("++2 dB", false, Mixxx::ReplayGain::kRatioUndefined); +TEST_F(ReplayGainTest, RatioFromStringInvalid) { + ratioFromString("", false, Mixxx::ReplayGain::kRatioUndefined); + ratioFromString("abcde", false, Mixxx::ReplayGain::kRatioUndefined); + ratioFromString("0 dBA", false, Mixxx::ReplayGain::kRatioUndefined); + ratioFromString("--2 dB", false, Mixxx::ReplayGain::kRatioUndefined); + ratioFromString("+-2 dB", false, Mixxx::ReplayGain::kRatioUndefined); + ratioFromString("-+2 dB", false, Mixxx::ReplayGain::kRatioUndefined); + ratioFromString("++2 dB", false, Mixxx::ReplayGain::kRatioUndefined); } TEST_F(ReplayGainTest, NormalizeRatio) { @@ -98,30 +98,30 @@ TEST_F(ReplayGainTest, NormalizeRatio) { normalizeRatio(-Mixxx::ReplayGain::kRatio0dB); } -TEST_F(ReplayGainTest, ParsePeakValid) { - parsePeak("0", true, Mixxx::ReplayGain::kPeakMin); - parsePeak("+0", true, Mixxx::ReplayGain::kPeakMin); - parsePeak("-0", true, Mixxx::ReplayGain::kPeakMin); - parsePeak("0.0", true, Mixxx::ReplayGain::kPeakMin); - parsePeak("+0.0", true, Mixxx::ReplayGain::kPeakMin); - parsePeak("-0.0", true, Mixxx::ReplayGain::kPeakMin); - parsePeak("1", true, Mixxx::ReplayGain::kPeakClip); - parsePeak("+1", true, Mixxx::ReplayGain::kPeakClip); - parsePeak("1.0", true, Mixxx::ReplayGain::kPeakClip); - parsePeak("+1.0", true, Mixxx::ReplayGain::kPeakClip); - parsePeak(" 0.12345 ", true, 0.12345); - parsePeak(" 1.2345", true, 1.2345); +TEST_F(ReplayGainTest, PeakFromStringValid) { + peakFromString("0", true, Mixxx::ReplayGain::kPeakMin); + peakFromString("+0", true, Mixxx::ReplayGain::kPeakMin); + peakFromString("-0", true, Mixxx::ReplayGain::kPeakMin); + peakFromString("0.0", true, Mixxx::ReplayGain::kPeakMin); + peakFromString("+0.0", true, Mixxx::ReplayGain::kPeakMin); + peakFromString("-0.0", true, Mixxx::ReplayGain::kPeakMin); + peakFromString("1", true, Mixxx::ReplayGain::kPeakClip); + peakFromString("+1", true, Mixxx::ReplayGain::kPeakClip); + peakFromString("1.0", true, Mixxx::ReplayGain::kPeakClip); + peakFromString("+1.0", true, Mixxx::ReplayGain::kPeakClip); + peakFromString(" 0.12345 ", true, 0.12345); + peakFromString(" 1.2345", true, 1.2345); } -TEST_F(ReplayGainTest, ParsePeakInvalid) { - parsePeak("", false, Mixxx::ReplayGain::kPeakUndefined); - parsePeak("-1", false, Mixxx::ReplayGain::kPeakUndefined); - parsePeak("-0.12345", false, Mixxx::ReplayGain::kPeakUndefined); - parsePeak("--1.0", false, Mixxx::ReplayGain::kPeakUndefined); - parsePeak("+-1.0", false, Mixxx::ReplayGain::kPeakUndefined); - parsePeak("-+1.0", false, Mixxx::ReplayGain::kPeakUndefined); - parsePeak("++1.0", false, Mixxx::ReplayGain::kPeakUndefined); - parsePeak("+abcde", false, Mixxx::ReplayGain::kPeakUndefined); +TEST_F(ReplayGainTest, PeakFromStringInvalid) { + peakFromString("", false, Mixxx::ReplayGain::kPeakUndefined); + peakFromString("-1", false, Mixxx::ReplayGain::kPeakUndefined); + peakFromString("-0.12345", false, Mixxx::ReplayGain::kPeakUndefined); + peakFromString("--1.0", false, Mixxx::ReplayGain::kPeakUndefined); + peakFromString("+-1.0", false, Mixxx::ReplayGain::kPeakUndefined); + peakFromString("-+1.0", false, Mixxx::ReplayGain::kPeakUndefined); + peakFromString("++1.0", false, Mixxx::ReplayGain::kPeakUndefined); + peakFromString("+abcde", false, Mixxx::ReplayGain::kPeakUndefined); } TEST_F(ReplayGainTest, NormalizePeak) { diff --git a/src/util/replaygain.cpp b/src/util/replaygain.cpp index 05ad4d1fe27..47af56a9264 100644 --- a/src/util/replaygain.cpp +++ b/src/util/replaygain.cpp @@ -54,7 +54,7 @@ QString normalizeNumberString(const QString& number, bool* pValid) { } // anonymous namespace -double ReplayGain::parseGain2Ratio(QString dbGain, bool* pValid) { +double ReplayGain::ratioFromString(QString dbGain, bool* pValid) { if (pValid) { *pValid = false; } @@ -90,7 +90,7 @@ double ReplayGain::parseGain2Ratio(QString dbGain, bool* pValid) { return kRatioUndefined; } -QString ReplayGain::formatRatio2Gain(double ratio) { +QString ReplayGain::ratioToString(double ratio) { if (isValidRatio(ratio)) { return QString::number(ratio2db(ratio)) + kGainSuffix; } else { @@ -100,17 +100,17 @@ QString ReplayGain::formatRatio2Gain(double ratio) { double ReplayGain::normalizeRatio(double ratio) { if (isValidRatio(ratio)) { - const double normalizedRatio = parseGain2Ratio(formatRatio2Gain(ratio)); + const double normalizedRatio = ratioFromString(ratioToString(ratio)); // NOTE(uklotzde): Subsequently formatting and parsing the // normalized value should not alter it anymore! - DEBUG_ASSERT(normalizedRatio == parseGain2Ratio(formatRatio2Gain(normalizedRatio))); + DEBUG_ASSERT(normalizedRatio == ratioFromString(ratioToString(normalizedRatio))); return normalizedRatio; } else { return kRatioUndefined; } } -CSAMPLE ReplayGain::parsePeak(QString strPeak, bool* pValid) { +CSAMPLE ReplayGain::peakFromString(QString strPeak, bool* pValid) { if (pValid) { *pValid = false; } @@ -136,7 +136,7 @@ CSAMPLE ReplayGain::parsePeak(QString strPeak, bool* pValid) { return kPeakUndefined; } -QString ReplayGain::formatPeak(CSAMPLE peak) { +QString ReplayGain::peakToString(CSAMPLE peak) { if (isValidPeak(peak)) { return QString::number(peak); } else { @@ -146,10 +146,10 @@ QString ReplayGain::formatPeak(CSAMPLE peak) { CSAMPLE ReplayGain::normalizePeak(CSAMPLE peak) { if (isValidPeak(peak)) { - const CSAMPLE normalizedPeak = parsePeak(formatPeak(peak)); + const CSAMPLE normalizedPeak = peakFromString(peakToString(peak)); // NOTE(uklotzde): Subsequently formatting and parsing the // normalized value should not alter it anymore! - DEBUG_ASSERT(normalizedPeak == parsePeak(formatPeak(normalizedPeak))); + DEBUG_ASSERT(normalizedPeak == peakFromString(peakToString(normalizedPeak))); return normalizedPeak; } else { return kPeakUndefined; diff --git a/src/util/replaygain.h b/src/util/replaygain.h index 2ea9daf2ea6..f91cb2dbac6 100644 --- a/src/util/replaygain.h +++ b/src/util/replaygain.h @@ -60,8 +60,10 @@ class ReplayGain final { m_ratio = kRatioUndefined; } - static double parseGain2Ratio(QString dBGain, bool* pValid = 0); - static QString formatRatio2Gain(double ratio); + // Parsing and formatting of gain values according to the + // ReplayGain 1.0/2.0 specification. + static double ratioFromString(QString dBGain, bool* pValid = 0); + static QString ratioToString(double ratio); static double normalizeRatio(double ratio); @@ -82,8 +84,10 @@ class ReplayGain final { m_peak = CSAMPLE_PEAK; } - static CSAMPLE parsePeak(QString strPeak, bool* pValid = 0); - static QString formatPeak(CSAMPLE peak); + // Parsing and formatting of peak amplitude values according to + // the ReplayGain 1.0/2.0 specification. + static CSAMPLE peakFromString(QString strPeak, bool* pValid = 0); + static QString peakToString(CSAMPLE peak); static CSAMPLE normalizePeak(CSAMPLE peak); From 10b31ed6e230f808b131ca7add8641a0f0a32ecf Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Tue, 3 Nov 2015 19:40:49 +0100 Subject: [PATCH 15/20] ReplayGain: Add test for parsing 0 dB strings --- src/test/replaygaintest.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/test/replaygaintest.cpp b/src/test/replaygaintest.cpp index 81ff73aeb46..354c7d6dc91 100644 --- a/src/test/replaygaintest.cpp +++ b/src/test/replaygaintest.cpp @@ -61,6 +61,14 @@ class ReplayGainTest : public testing::Test { } }; +TEST_F(ReplayGainTest, RatioFromString0dB) { + ratioFromString("0 dB", true, Mixxx::ReplayGain::kRatio0dB); + ratioFromString("0.0dB", true, Mixxx::ReplayGain::kRatio0dB); + ratioFromString("0 DB", true, Mixxx::ReplayGain::kRatio0dB); + ratioFromString("-0 Db", true, Mixxx::ReplayGain::kRatio0dB); + ratioFromString("+0db", true, Mixxx::ReplayGain::kRatio0dB); +} + TEST_F(ReplayGainTest, RatioFromStringValidRange) { for (int replayGainDb = -100; 100 >= replayGainDb; ++replayGainDb) { const QString inputValues[] = { From 939fb659608ed0a04ecaf1f3805359816de2c1cf Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Wed, 4 Nov 2015 22:46:01 +0100 Subject: [PATCH 16/20] Add "Replay Gain" column to browse view --- src/library/browse/browsetablemodel.cpp | 7 +++++-- src/library/browse/browsetablemodel.h | 1 + src/library/browse/browsethread.cpp | 7 +++++++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/library/browse/browsetablemodel.cpp b/src/library/browse/browsetablemodel.cpp index 9956bc76c79..f58672f0a55 100644 --- a/src/library/browse/browsetablemodel.cpp +++ b/src/library/browse/browsetablemodel.cpp @@ -39,6 +39,7 @@ BrowseTableModel::BrowseTableModel(QObject* parent, header_data.insert(COLUMN_KEY, tr("Key")); header_data.insert(COLUMN_TYPE, tr("Type")); header_data.insert(COLUMN_BITRATE, tr("Bitrate")); + header_data.insert(COLUMN_REPLAYGAIN, tr("Replay Gain")); header_data.insert(COLUMN_LOCATION, tr("Location")); header_data.insert(COLUMN_ALBUMARTIST, tr("Album Artist")); header_data.insert(COLUMN_GROUPING, tr("Grouping")); @@ -151,7 +152,8 @@ bool BrowseTableModel::isColumnHiddenByDefault(int column) { column == COLUMN_GROUPING || column == COLUMN_LOCATION || column == COLUMN_ALBUMARTIST || - column == COLUMN_FILE_CREATION_TIME) { + column == COLUMN_FILE_CREATION_TIME || + column == COLUMN_REPLAYGAIN) { return true; } return false; @@ -305,7 +307,8 @@ Qt::ItemFlags BrowseTableModel::flags(const QModelIndex &index) const { column == COLUMN_DURATION || column == COLUMN_TYPE || column == COLUMN_FILE_MODIFIED_TIME || - column == COLUMN_FILE_CREATION_TIME) { + column == COLUMN_FILE_CREATION_TIME || + column == COLUMN_REPLAYGAIN) { return defaultFlags; } else { return defaultFlags | Qt::ItemIsEditable; diff --git a/src/library/browse/browsetablemodel.h b/src/library/browse/browsetablemodel.h index c06d5486579..9302a911f05 100644 --- a/src/library/browse/browsetablemodel.h +++ b/src/library/browse/browsetablemodel.h @@ -31,6 +31,7 @@ const int COLUMN_ALBUMARTIST = 16; const int COLUMN_GROUPING = 17; const int COLUMN_FILE_MODIFIED_TIME = 18; const int COLUMN_FILE_CREATION_TIME = 19; +const int COLUMN_REPLAYGAIN = 20; // The BrowseTable models displays tracks diff --git a/src/library/browse/browsethread.cpp b/src/library/browse/browsethread.cpp index ee051728d55..0a00f3ea9f3 100644 --- a/src/library/browse/browsethread.cpp +++ b/src/library/browse/browsethread.cpp @@ -255,6 +255,13 @@ void BrowseThread::populateModel() { item->setData(creationTime, Qt::UserRole); row_data.insert(COLUMN_FILE_CREATION_TIME, item); + const Mixxx::ReplayGain replayGain(tio.getReplayGain()); + item = new QStandardItem( + Mixxx::ReplayGain::ratioToString(replayGain.getRatio())); + item->setToolTip(item->text()); + item->setData(item->text(), Qt::UserRole); + row_data.insert(COLUMN_REPLAYGAIN, item); + rows.append(row_data); ++row; // If 10 tracks have been analyzed, send it to GUI From a1beb561ee417c058d55a3e8a91dd7c6991c4512 Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Wed, 4 Nov 2015 23:10:04 +0100 Subject: [PATCH 17/20] Add "Replay Gain" column to library view --- src/library/basesqltablemodel.cpp | 13 +++++++++---- src/library/basetrackcache.cpp | 5 ++++- src/library/librarytablemodel.cpp | 1 - src/library/mixxxlibraryfeature.cpp | 1 + 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/library/basesqltablemodel.cpp b/src/library/basesqltablemodel.cpp index 1314d1f165c..e76dc4ae14d 100644 --- a/src/library/basesqltablemodel.cpp +++ b/src/library/basesqltablemodel.cpp @@ -105,6 +105,8 @@ void BaseSqlTableModel::initHeaderData() { tr("Preview"), 50); setHeaderProperties(ColumnCache::COLUMN_LIBRARYTABLE_COVERART, tr("Cover Art"), 90); + setHeaderProperties(ColumnCache::COLUMN_LIBRARYTABLE_REPLAYGAIN, + tr("Replay Gain"), 50); } QSqlDatabase BaseSqlTableModel::database() const { @@ -172,7 +174,8 @@ bool BaseSqlTableModel::isColumnHiddenByDefault(int column) { (column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_YEAR)) || (column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_GROUPING)) || (column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_LOCATION)) || - (column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_ALBUMARTIST))) { + (column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_ALBUMARTIST)) || + (column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_REPLAYGAIN))) { return true; } return false; @@ -613,8 +616,9 @@ QVariant BaseSqlTableModel::data(const QModelIndex& index, int role) const { value = KeyUtils::keyToString(key); } } - // Otherwise, just use the column value. - } + } else if (column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_REPLAYGAIN)) { + value = Mixxx::ReplayGain::ratioToString(value.toDouble()); + } // Otherwise, just use the column value. break; case Qt::EditRole: @@ -722,7 +726,8 @@ Qt::ItemFlags BaseSqlTableModel::readWriteFlags( column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_DURATION) || column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_BITRATE) || column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_DATETIMEADDED) || - column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_COVERART)) { + column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_COVERART) || + column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_REPLAYGAIN)) { return defaultFlags; } else if (column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_TIMESPLAYED)) { return defaultFlags | Qt::ItemIsUserCheckable; diff --git a/src/library/basetrackcache.cpp b/src/library/basetrackcache.cpp index e8e835e3b64..5742bd48ec1 100644 --- a/src/library/basetrackcache.cpp +++ b/src/library/basetrackcache.cpp @@ -307,6 +307,8 @@ void BaseTrackCache::getTrackValueForColumn(TrackPointer pTrack, trackValue.setValue(pTrack->getBitrate()); } else if (fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_BPM) == column) { trackValue.setValue(pTrack->getBpm()); + } else if (fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_REPLAYGAIN) == column) { + trackValue.setValue(pTrack->getReplayGain().getRatio()); } else if (fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_PLAYED) == column) { trackValue.setValue(pTrack->getPlayed()); } else if (fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_TIMESPLAYED) == column) { @@ -589,7 +591,8 @@ int BaseTrackCache::compareColumnValues(int sortColumn, Qt::SortOrder sortOrder, sortColumn == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_BPM) || sortColumn == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_DURATION) || sortColumn == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_TIMESPLAYED) || - sortColumn == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_RATING)) { + sortColumn == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_RATING) || + sortColumn == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_REPLAYGAIN)) { // Sort as floats. double delta = val1.toDouble() - val2.toDouble(); diff --git a/src/library/librarytablemodel.cpp b/src/library/librarytablemodel.cpp index cef3bd5b93e..260210b8340 100644 --- a/src/library/librarytablemodel.cpp +++ b/src/library/librarytablemodel.cpp @@ -65,7 +65,6 @@ bool LibraryTableModel::isColumnInternal(int column) { if ((column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_ID)) || (column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_URL)) || (column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_CUEPOINT)) || - (column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_REPLAYGAIN)) || (column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_WAVESUMMARYHEX)) || (column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_SAMPLERATE)) || (column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_MIXXXDELETED)) || diff --git a/src/library/mixxxlibraryfeature.cpp b/src/library/mixxxlibraryfeature.cpp index 1fc22613f47..c299e1c9606 100644 --- a/src/library/mixxxlibraryfeature.cpp +++ b/src/library/mixxxlibraryfeature.cpp @@ -53,6 +53,7 @@ MixxxLibraryFeature::MixxxLibraryFeature(Library* pLibrary, << "library." + LIBRARYTABLE_BPM_LOCK << "library." + LIBRARYTABLE_DURATION << "library." + LIBRARYTABLE_BITRATE + << "library." + LIBRARYTABLE_REPLAYGAIN << "library." + LIBRARYTABLE_FILETYPE << "library." + LIBRARYTABLE_DATETIMEADDED << "track_locations.location" From 613305c224cd4c31239fb909f86dcbc0624a72c5 Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Wed, 4 Nov 2015 23:11:43 +0100 Subject: [PATCH 18/20] Re-enable tooltip for calendar year --- src/library/basesqltablemodel.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/library/basesqltablemodel.cpp b/src/library/basesqltablemodel.cpp index e76dc4ae14d..e12e228e221 100644 --- a/src/library/basesqltablemodel.cpp +++ b/src/library/basesqltablemodel.cpp @@ -585,9 +585,7 @@ QVariant BaseSqlTableModel::data(const QModelIndex& index, int role) const { } else if (column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_BPM_LOCK)) { value = value.toBool(); } else if (column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_YEAR)) { - if (Qt::DisplayRole == role) { - value = Mixxx::TrackMetadata::formatCalendarYear(value.toString()); - } + value = Mixxx::TrackMetadata::formatCalendarYear(value.toString()); } else if (column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_TRACKNUMBER)) { int track_number = value.toInt(); if (track_number <= 0) { From 929649f9d05d2027a63b198c1783c1c0bc705106 Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Wed, 4 Nov 2015 23:45:29 +0100 Subject: [PATCH 19/20] Add "Replay Gain" field to track info dialog --- src/dlgtrackinfo.cpp | 3 ++ src/dlgtrackinfo.ui | 70 ++++++++++++++++++++++++++++++-------------- 2 files changed, 51 insertions(+), 22 deletions(-) diff --git a/src/dlgtrackinfo.cpp b/src/dlgtrackinfo.cpp index 9d7db9a3171..e38adb2a8b3 100644 --- a/src/dlgtrackinfo.cpp +++ b/src/dlgtrackinfo.cpp @@ -159,6 +159,8 @@ void DlgTrackInfo::populateFields(TrackPointer pTrack) { txtBitrate->setText(QString(pTrack->getBitrateStr()) + (" ") + tr("kbps")); txtBpm->setText(pTrack->getBpmStr()); txtKey->setText(pTrack->getKeyText()); + const Mixxx::ReplayGain replayGain(pTrack->getReplayGain()); + txtReplayGain->setText(Mixxx::ReplayGain::ratioToString(replayGain.getRatio())); BeatsPointer pBeats = pTrack->getBeats(); bool beatsSupportsSet = !pBeats || (pBeats->getCapabilities() & Beats::BEATSCAP_SET); bool enableBpmEditing = !pTrack->hasBpmLock() && beatsSupportsSet; @@ -434,6 +436,7 @@ void DlgTrackInfo::clear() { txtLocation->setPlainText(""); txtBitrate->setText(""); txtBpm->setText(""); + txtReplayGain->setText(""); m_cueMap.clear(); cueTable->clearContents(); diff --git a/src/dlgtrackinfo.ui b/src/dlgtrackinfo.ui index 50e00c51578..b13952a0f2b 100644 --- a/src/dlgtrackinfo.ui +++ b/src/dlgtrackinfo.ui @@ -473,7 +473,7 @@ - + @@ -490,6 +490,16 @@ + + + + + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + @@ -506,8 +516,18 @@ - - + + + + + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + 75 @@ -515,25 +535,15 @@ - Location: + Replay Gain: - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - - - - - - - - Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - + + @@ -542,7 +552,7 @@ - + @@ -558,7 +568,7 @@ - + @@ -568,7 +578,7 @@ - + @@ -584,7 +594,7 @@ - + @@ -594,7 +604,23 @@ - + + + + + 75 + true + + + + Location: + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + true From 72b6c36f96b055d4a639f4bcb2baf4dec818b10c Mon Sep 17 00:00:00 2001 From: Uwe Klotz Date: Thu, 5 Nov 2015 00:12:04 +0100 Subject: [PATCH 20/20] Add context menu item for resetting the replay gain --- src/widget/wtracktableview.cpp | 25 ++++++++++++++++++++++++- src/widget/wtracktableview.h | 4 ++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/widget/wtracktableview.cpp b/src/widget/wtracktableview.cpp index 4b74a81642e..758aa301f68 100644 --- a/src/widget/wtracktableview.cpp +++ b/src/widget/wtracktableview.cpp @@ -146,6 +146,7 @@ WTrackTableView::~WTrackTableView() { delete m_pBpmTwoThirdsAction; delete m_pBpmThreeFourthsAction; delete m_pBPMMenu; + delete m_pReplayGainResetAction; delete m_pPurgeAct; delete m_pFileBrowserAct; delete m_pResetPlayedAct; @@ -449,6 +450,10 @@ void WTrackTableView::createActions() { m_pClearBeatsAction = new QAction(tr("Clear BPM and Beatgrid"), this); connect(m_pClearBeatsAction, SIGNAL(triggered()), this, SLOT(slotClearBeats())); + + m_pReplayGainResetAction = new QAction(tr("Reset Replay Gain"), this); + connect(m_pReplayGainResetAction, SIGNAL(triggered()), + this, SLOT(slotReplayGainReset())); } // slot @@ -870,7 +875,8 @@ void WTrackTableView::contextMenuEvent(QContextMenuEvent* event) { m_pBPMMenu->addAction(m_pClearBeatsAction); } - bool locked = modelHasCapabilities(TrackModel::TRACKMODELCAPS_LOCKED); + m_pMenu->addAction(m_pReplayGainResetAction); + m_pMenu->addSeparator(); if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_RELOADMETADATA)) { m_pMenu->addAction(m_pReloadMetadataAct); @@ -899,6 +905,7 @@ void WTrackTableView::contextMenuEvent(QContextMenuEvent* event) { // REMOVE and HIDE should not be at the first menu position to avoid accidental clicks m_pMenu->addSeparator(); + bool locked = modelHasCapabilities(TrackModel::TRACKMODELCAPS_LOCKED); if (modelHasCapabilities(TrackModel::TRACKMODELCAPS_REMOVE)) { m_pRemoveAct->setEnabled(!locked); m_pMenu->addAction(m_pRemoveAct); @@ -1590,6 +1597,22 @@ void WTrackTableView::slotClearBeats() { } } +void WTrackTableView::slotReplayGainReset() { + QModelIndexList indices = selectionModel()->selectedRows(); + TrackModel* trackModel = getTrackModel(); + + if (trackModel == NULL) { + return; + } + + foreach (QModelIndex index, indices) { + TrackPointer pTrack = trackModel->getTrack(index); + if (pTrack) { + pTrack->setReplayGain(Mixxx::ReplayGain()); + } + } +} + void WTrackTableView::slotCoverArtSelected(const CoverArt& art) { TrackModel* trackModel = getTrackModel(); if (trackModel == NULL) { diff --git a/src/widget/wtracktableview.h b/src/widget/wtracktableview.h index ff6012ce954..881f5938a00 100644 --- a/src/widget/wtracktableview.h +++ b/src/widget/wtracktableview.h @@ -72,6 +72,7 @@ class WTrackTableView : public WLibraryTableView { void slotUnlockBpm(); void slotScaleBpm(int); void slotClearBeats(); + void slotReplayGainReset(); // Signalled 20 times per second (every 50ms) by GuiTick. void slotGuiTick50ms(double); void slotScrollValueChanged(int); @@ -155,6 +156,9 @@ class WTrackTableView : public WLibraryTableView { // Clear track beats QAction* m_pClearBeatsAction; + // Replay Gain feature + QAction *m_pReplayGainResetAction; + bool m_sorting; // Column numbers