diff --git a/build/depends.py b/build/depends.py index f38411d76b5..2a9015240f1 100644 --- a/build/depends.py +++ b/build/depends.py @@ -1045,6 +1045,7 @@ def sources(self, build): "util/statsmanager.cpp", "util/stat.cpp", "util/statmodel.cpp", + "util/duration.cpp", "util/time.cpp", "util/timer.cpp", "util/performancetimer.cpp", diff --git a/res/schema.xml b/res/schema.xml index de6d75a8d23..e85570fb316 100644 --- a/res/schema.xml +++ b/res/schema.xml @@ -40,7 +40,7 @@ METADATA genre varchar(32), tracknumber varchar(3), location varchar(512) REFERENCES track_locations(location), comment varchar(20), url varchar(256), - duration integer, + duration float, bitrate integer, samplerate integer, cuepoint integer, bpm float, wavesummaryhex blob, @@ -113,7 +113,7 @@ METADATA location integer REFERENCES track_locations(location), comment varchar(256), url varchar(256), - duration integer, + duration float, bitrate integer, samplerate integer, cuepoint integer, diff --git a/src/library/autodj/autodjprocessor.cpp b/src/library/autodj/autodjprocessor.cpp index c88a13824be..080785df209 100644 --- a/src/library/autodj/autodjprocessor.cpp +++ b/src/library/autodj/autodjprocessor.cpp @@ -9,7 +9,7 @@ #define kConfigKey "[Auto DJ]" const char* kTransitionPreferenceName = "Transition"; -const int kTransitionPreferenceDefault = 10; +const double kTransitionPreferenceDefault = 10.0; static const bool sDebug = false; @@ -73,7 +73,7 @@ AutoDJProcessor::AutoDJProcessor(QObject* pParent, m_pPlayerManager(pPlayerManager), m_pAutoDJTableModel(NULL), m_eState(ADJ_DISABLED), - m_iTransitionTime(kTransitionPreferenceDefault), + m_transitionTime(kTransitionPreferenceDefault), m_nextTransitionTime(kTransitionPreferenceDefault) { m_pAutoDJTableModel = new PlaylistTableModel(this, pTrackCollection, "mixxx.db.model.autodj"); @@ -123,8 +123,8 @@ AutoDJProcessor::AutoDJProcessor(QObject* pParent, QString str_autoDjTransition = m_pConfig->getValueString( ConfigKey(kConfigKey, kTransitionPreferenceName)); if (!str_autoDjTransition.isEmpty()) { - m_iTransitionTime = str_autoDjTransition.toInt(); - m_nextTransitionTime = m_iTransitionTime; + m_transitionTime = str_autoDjTransition.toDouble(); + m_nextTransitionTime = m_transitionTime; } } @@ -707,13 +707,13 @@ void AutoDJProcessor::calculateTransition(DeckAttributes* pFromDeck, if (fromTrack) { // TODO(rryan): Duration is super inaccurate! We should be using // track_samples / track_samplerate instead. - int fromTrackDuration = fromTrack->getDuration(); + double fromTrackDuration = fromTrack->getDuration(); qDebug() << fromTrack->getLocation() << "fromTrackDuration =" << fromTrackDuration; // The track might be shorter than the transition period. Use a // sensible cap. - m_nextTransitionTime = math_min(m_iTransitionTime, + m_nextTransitionTime = math_min(m_transitionTime, fromTrackDuration / 2); if (pToDeck) { @@ -721,7 +721,7 @@ void AutoDJProcessor::calculateTransition(DeckAttributes* pFromDeck, if (toTrack) { // TODO(rryan): Duration is super inaccurate! We should be using // track_samples / track_samplerate instead. - int toTrackDuration = toTrack->getDuration(); + double toTrackDuration = toTrack->getDuration(); qDebug() << toTrack->getLocation() << "toTrackDuration = " << toTrackDuration; m_nextTransitionTime = math_min(m_nextTransitionTime, @@ -729,15 +729,13 @@ void AutoDJProcessor::calculateTransition(DeckAttributes* pFromDeck, } } - if (fromTrackDuration > 0) { - pFromDeck->fadeDuration = - static_cast(m_nextTransitionTime) / - static_cast(fromTrackDuration); + if (fromTrackDuration > 0.0) { + pFromDeck->fadeDuration = m_nextTransitionTime / fromTrackDuration; } else { - pFromDeck->fadeDuration = 0; + pFromDeck->fadeDuration = 0.0; } - if (m_nextTransitionTime > 0) { + if (m_nextTransitionTime > 0.0) { pFromDeck->posThreshold = 1.0 - pFromDeck->fadeDuration; } else { // in case of pause transition @@ -826,7 +824,7 @@ void AutoDJProcessor::setTransitionTime(int time) { // Update the transition time first. m_pConfig->set(ConfigKey(kConfigKey, kTransitionPreferenceName), ConfigValue(time)); - m_iTransitionTime = time; + m_transitionTime = time; // Then re-calculate fade thresholds for the decks. if (m_eState == ADJ_IDLE) { diff --git a/src/library/autodj/autodjprocessor.h b/src/library/autodj/autodjprocessor.h index f243e6f3134..8a560a50b9b 100644 --- a/src/library/autodj/autodjprocessor.h +++ b/src/library/autodj/autodjprocessor.h @@ -123,8 +123,8 @@ class AutoDJProcessor : public QObject { return m_eState; } - int getTransitionTime() const { - return m_iTransitionTime; + double getTransitionTime() const { + return m_transitionTime; } PlaylistTableModel* getTableModel() const { @@ -197,8 +197,8 @@ class AutoDJProcessor : public QObject { PlaylistTableModel* m_pAutoDJTableModel; AutoDJState m_eState; - int m_iTransitionTime; // the desired value set by the user - int m_nextTransitionTime; // the tweaked value actually used + double m_transitionTime; // the desired value set by the user + double m_nextTransitionTime; // the tweaked value actually used QList m_decks; diff --git a/src/library/autodj/dlgautodj.cpp b/src/library/autodj/dlgautodj.cpp index 5b0569ef144..9b2197e422e 100644 --- a/src/library/autodj/dlgautodj.cpp +++ b/src/library/autodj/dlgautodj.cpp @@ -5,7 +5,7 @@ #include "library/playlisttablemodel.h" #include "widget/wtracktableview.h" #include "util/assert.h" -#include "util/time.h" +#include "util/duration.h" DlgAutoDJ::DlgAutoDJ(QWidget* parent, UserSettingsPointer pConfig, @@ -200,7 +200,7 @@ void DlgAutoDJ::setTrackTableRowHeight(int rowHeight) { } void DlgAutoDJ::updateSelectionInfo() { - int duration = 0; + double duration = 0.0; QModelIndexList indices = m_pTrackTableView->selectionModel()->selectedRows(); @@ -214,7 +214,7 @@ void DlgAutoDJ::updateSelectionInfo() { QString label; if (!indices.isEmpty()) { - label.append(Time::formatSeconds(duration)); + label.append(mixxx::Duration::formatSeconds(duration)); label.append(QString(" (%1)").arg(indices.size())); labelSelectionInfo->setText(label); labelSelectionInfo->setEnabled(true); diff --git a/src/library/banshee/bansheeplaylistmodel.cpp b/src/library/banshee/bansheeplaylistmodel.cpp index 49e222ecbe2..ec162d478ca 100644 --- a/src/library/banshee/bansheeplaylistmodel.cpp +++ b/src/library/banshee/bansheeplaylistmodel.cpp @@ -305,7 +305,7 @@ TrackPointer BansheePlaylistModel::getTrack(const QModelIndex& index) const { if (pTrack && !track_already_in_library) { pTrack->setArtist(getFieldString(index, CLM_ARTIST)); pTrack->setTitle(getFieldString(index, CLM_TITLE)); - pTrack->setDuration(getFieldString(index, CLM_DURATION).toInt()); + pTrack->setDuration(getFieldString(index, CLM_DURATION).toDouble()); pTrack->setAlbum(getFieldString(index, CLM_ALBUM)); pTrack->setAlbumArtist(getFieldString(index, CLM_ALBUM_ARTIST)); pTrack->setYear(getFieldString(index, CLM_YEAR)); diff --git a/src/library/basesqltablemodel.cpp b/src/library/basesqltablemodel.cpp index 6890264dd44..f1d855064da 100644 --- a/src/library/basesqltablemodel.cpp +++ b/src/library/basesqltablemodel.cpp @@ -16,7 +16,7 @@ #include "mixer/playerinfo.h" #include "track/keyutils.h" #include "track/trackmetadata.h" -#include "util/time.h" +#include "util/duration.h" #include "util/dnd.h" #include "util/assert.h" #include "util/performancetimer.h" @@ -615,7 +615,7 @@ QVariant BaseSqlTableModel::data(const QModelIndex& index, int role) const { if (column == fieldIndex(ColumnCache::COLUMN_LIBRARYTABLE_DURATION)) { int duration = value.toInt(); if (duration > 0) { - value = Time::formatSeconds(duration); + value = mixxx::Duration::formatSeconds(duration); } else { value = QString(); } diff --git a/src/library/browse/browsethread.cpp b/src/library/browse/browsethread.cpp index 7af8c0afd78..dddac5399b7 100644 --- a/src/library/browse/browsethread.cpp +++ b/src/library/browse/browsethread.cpp @@ -10,7 +10,6 @@ #include "library/browse/browsetablemodel.h" #include "sources/soundsourceproxy.h" #include "track/trackmetadata.h" -#include "util/time.h" #include "util/trace.h" @@ -213,7 +212,7 @@ void BrowseThread::populateModel() { item->setData(item->text(), Qt::UserRole); row_data.insert(COLUMN_COMMENT, item); - QString duration = Time::formatSeconds(pTrack->getDuration()); + QString duration = pTrack->getDurationText(mixxx::Duration::Precision::SECONDS); item = new QStandardItem(duration); item->setToolTip(item->text()); item->setData(item->text(), Qt::UserRole); diff --git a/src/library/cratefeature.cpp b/src/library/cratefeature.cpp index a0f8b118165..0d53d49e321 100644 --- a/src/library/cratefeature.cpp +++ b/src/library/cratefeature.cpp @@ -23,7 +23,7 @@ #include "treeitem.h" #include "sources/soundsourceproxy.h" #include "util/dnd.h" -#include "util/time.h" +#include "util/duration.h" CrateFeature::CrateFeature(Library* pLibrary, TrackCollection* pTrackCollection, @@ -498,7 +498,7 @@ void CrateFeature::buildCrateList() { crateListTableModel.index(row, durationColumn)).toInt(); m_crateList.append(qMakePair(id, QString("%1 (%2) %3") .arg(name, QString::number(count), - Time::formatSeconds(duration)))); + mixxx::Duration::formatSeconds(duration)))); } } diff --git a/src/library/dao/trackdao.cpp b/src/library/dao/trackdao.cpp index 4f448027712..724860d2ada 100644 --- a/src/library/dao/trackdao.cpp +++ b/src/library/dao/trackdao.cpp @@ -1088,7 +1088,7 @@ bool setTrackUrl(const QSqlRecord& record, const int column, bool setTrackDuration(const QSqlRecord& record, const int column, TrackPointer pTrack) { - pTrack->setDuration(record.value(column).toInt()); + pTrack->setDuration(record.value(column).toDouble()); return false; } diff --git a/src/library/dlgtrackinfo.cpp b/src/library/dlgtrackinfo.cpp index d3998a9648c..a83c9321b6b 100644 --- a/src/library/dlgtrackinfo.cpp +++ b/src/library/dlgtrackinfo.cpp @@ -165,7 +165,7 @@ void DlgTrackInfo::populateFields(const Track& track) { txtComment->setPlainText(track.getComment()); // Non-editable fields - txtDuration->setText(track.getDurationText()); + txtDuration->setText(track.getDurationText(mixxx::Duration::Precision::SECONDS)); txtLocation->setPlainText(track.getLocation()); txtType->setText(track.getType()); txtBitrate->setText(QString(track.getBitrateText()) + (" ") + tr("kbps")); diff --git a/src/library/playlistfeature.cpp b/src/library/playlistfeature.cpp index 902709d74b5..fff0ed5bfbb 100644 --- a/src/library/playlistfeature.cpp +++ b/src/library/playlistfeature.cpp @@ -16,7 +16,7 @@ #include "controllers/keyboard/keyboardeventfilter.h" #include "sources/soundsourceproxy.h" #include "util/dnd.h" -#include "util/time.h" +#include "util/duration.h" PlaylistFeature::PlaylistFeature(QObject* parent, TrackCollection* pTrackCollection, @@ -170,7 +170,7 @@ void PlaylistFeature::buildPlaylistList() { playlistTableModel.index(row, durationColumn)).toInt(); m_playlistList.append(qMakePair(id, QString("%1 (%2) %3") .arg(name, QString::number(count), - Time::formatSeconds(duration)))); + mixxx::Duration::formatSeconds(duration)))); } } diff --git a/src/musicbrainz/tagfetcher.cpp b/src/musicbrainz/tagfetcher.cpp index 516f641571e..9a7320f1583 100644 --- a/src/musicbrainz/tagfetcher.cpp +++ b/src/musicbrainz/tagfetcher.cpp @@ -85,7 +85,7 @@ void TagFetcher::fingerprintFound(int index) { emit(fetchProgress(tr("Identifying track"))); // qDebug() << "start to look up the MBID"; - m_AcoustidClient.start(index, fingerprint, ptrack->getDuration()); + m_AcoustidClient.start(index, fingerprint, ptrack->getDurationInt()); } void TagFetcher::mbidFound(int index, const QString& mbid) { diff --git a/src/sources/audiosource.h b/src/sources/audiosource.h index b5d123597fb..21e83fdcb1a 100644 --- a/src/sources/audiosource.h +++ b/src/sources/audiosource.h @@ -47,9 +47,9 @@ class AudioSource: public UrlResource, public AudioSignal { inline bool hasDuration() const { return isValid(); } - inline SINT getDuration() const { + inline double getDuration() const { DEBUG_ASSERT(hasDuration()); // prevents division by zero - return getFrameCount() / getSamplingRate(); + return double(getFrameCount()) / double(getSamplingRate()); } // The bitrate is measured in kbit/s (kbps). diff --git a/src/sources/soundsourcemodplug.cpp b/src/sources/soundsourcemodplug.cpp index 627e2300405..bfeaf9cb007 100644 --- a/src/sources/soundsourcemodplug.cpp +++ b/src/sources/soundsourcemodplug.cpp @@ -77,7 +77,7 @@ Result SoundSourceModPlug::parseTrackMetadataAndCoverArt( if (nullptr != pModFile) { pTrackMetadata->setComment(QString(ModPlug::ModPlug_GetMessage(pModFile))); pTrackMetadata->setTitle(QString(ModPlug::ModPlug_GetName(pModFile))); - pTrackMetadata->setDuration(ModPlug::ModPlug_GetLength(pModFile) / 1000); + pTrackMetadata->setDuration(ModPlug::ModPlug_GetLength(pModFile) / 1000.0); pTrackMetadata->setBitrate(8); // not really, but fill in something... ModPlug::ModPlug_Unload(pModFile); } diff --git a/src/sources/soundsourceopus.cpp b/src/sources/soundsourceopus.cpp index 4a87feea229..932ab5882da 100644 --- a/src/sources/soundsourceopus.cpp +++ b/src/sources/soundsourceopus.cpp @@ -83,8 +83,9 @@ Result SoundSourceOpus::parseTrackMetadataAndCoverArt( pTrackMetadata->setChannels(op_channel_count(l_ptrOpusFile, -1)); pTrackMetadata->setSampleRate(kSamplingRate); pTrackMetadata->setBitrate(op_bitrate(l_ptrOpusFile, -1) / 1000); - pTrackMetadata->setDuration( - op_pcm_total(l_ptrOpusFile, -1) / pTrackMetadata->getSampleRate()); + // Cast to double is required for duration with sub-second precision + const double dTotalFrames = op_pcm_total(l_ptrOpusFile, -1); + pTrackMetadata->setDuration(dTotalFrames / pTrackMetadata->getSampleRate()); bool hasDate = false; for (i = 0; i < l_ptrOpusTags->comments; ++i) { diff --git a/src/test/timeutiltest.cpp b/src/test/durationutiltest.cpp similarity index 56% rename from src/test/timeutiltest.cpp rename to src/test/durationutiltest.cpp index 248d9ed4536..03310aec51c 100644 --- a/src/test/timeutiltest.cpp +++ b/src/test/durationutiltest.cpp @@ -1,15 +1,15 @@ #include -#include "util/time.h" +#include "util/duration.h" #include namespace { -class TimeUtilTest : public testing::Test { +class DurationUtilTest : public testing::Test { protected: - TimeUtilTest() { + DurationUtilTest() { } virtual void SetUp() { @@ -20,13 +20,13 @@ class TimeUtilTest : public testing::Test { static QString adjustPrecision( QString withMilliseconds, - Time::Precision precision) { + mixxx::Duration::Precision precision) { switch (precision) { - case Time::Precision::SECONDS: + case mixxx::Duration::Precision::SECONDS: { return withMilliseconds.left(withMilliseconds.length() - 4); } - case Time::Precision::CENTISECONDS: + case mixxx::Duration::Precision::CENTISECONDS: { return withMilliseconds.left(withMilliseconds.length() - 1); } @@ -38,28 +38,28 @@ class TimeUtilTest : public testing::Test { void formatSeconds(QString expectedMilliseconds, double dSeconds) { ASSERT_LE(4, expectedMilliseconds.length()); // 3 digits + 1 decimal point const QString actualSeconds = - Time::formatSeconds(dSeconds, Time::Precision::SECONDS); + mixxx::Duration::formatSeconds(dSeconds, mixxx::Duration::Precision::SECONDS); const QString expectedSeconds = - adjustPrecision(expectedMilliseconds, Time::Precision::SECONDS); + adjustPrecision(expectedMilliseconds, mixxx::Duration::Precision::SECONDS); EXPECT_EQ(expectedSeconds, actualSeconds); const QString expectedCentiseconds = - adjustPrecision(expectedMilliseconds, Time::Precision::CENTISECONDS); + adjustPrecision(expectedMilliseconds, mixxx::Duration::Precision::CENTISECONDS); const QString actualCentiseconds = - Time::formatSeconds(dSeconds, Time::Precision::CENTISECONDS); + mixxx::Duration::formatSeconds(dSeconds, mixxx::Duration::Precision::CENTISECONDS); EXPECT_EQ(expectedCentiseconds, actualCentiseconds); const QString actualMilliseconds = - Time::formatSeconds(dSeconds, Time::Precision::MILLISECONDS); + mixxx::Duration::formatSeconds(dSeconds, mixxx::Duration::Precision::MILLISECONDS); EXPECT_EQ(actualMilliseconds, actualMilliseconds); } }; -TEST_F(TimeUtilTest, FormatSecondsNegative) { - EXPECT_EQ("?", Time::formatSeconds(-1, Time::Precision::SECONDS)); - EXPECT_EQ("?", Time::formatSeconds(-1, Time::Precision::CENTISECONDS)); - EXPECT_EQ("?", Time::formatSeconds(-1, Time::Precision::MILLISECONDS)); +TEST_F(DurationUtilTest, FormatSecondsNegative) { + EXPECT_EQ("?", mixxx::Duration::formatSeconds(-1, mixxx::Duration::Precision::SECONDS)); + EXPECT_EQ("?", mixxx::Duration::formatSeconds(-1, mixxx::Duration::Precision::CENTISECONDS)); + EXPECT_EQ("?", mixxx::Duration::formatSeconds(-1, mixxx::Duration::Precision::MILLISECONDS)); } -TEST_F(TimeUtilTest, FormatSeconds) { +TEST_F(DurationUtilTest, FormatSeconds) { formatSeconds("00:00.000", 0); formatSeconds("00:01.000", 1); formatSeconds("00:59.000", 59); diff --git a/src/track/track.cpp b/src/track/track.cpp index 973402982d3..1ed5b111073 100644 --- a/src/track/track.cpp +++ b/src/track/track.cpp @@ -11,7 +11,6 @@ #include "track/trackmetadatataglib.h" #include "util/assert.h" #include "util/compatibility.h" -#include "util/time.h" namespace { @@ -373,21 +372,38 @@ void Track::setDateAdded(const QDateTime& dateAdded) { m_dateAdded = dateAdded; } -void Track::setDuration(int iDuration) { +void Track::setDuration(double duration) { QMutexLocker lock(&m_qMutex); - if (m_metadata.getDuration() != iDuration) { - m_metadata.setDuration(iDuration); + if (m_metadata.getDuration() != duration) { + m_metadata.setDuration(duration); markDirtyAndUnlock(&lock); } } -int Track::getDuration() const { +double Track::getDuration(DurationRounding rounding) const { QMutexLocker lock(&m_qMutex); + switch (rounding) { + case DurationRounding::SECONDS: + return std::round(m_metadata.getDuration()); + case DurationRounding::NONE: + return m_metadata.getDuration(); + } + // unreachable code / avoid compiler warnings + DEBUG_ASSERT(!"unhandled enum value"); return m_metadata.getDuration(); } -QString Track::getDurationText() const { - return Time::formatSeconds(getDuration()); +QString Track::getDurationText(mixxx::Duration::Precision precision) const { + double duration; + if (precision == mixxx::Duration::Precision::SECONDS) { + // Round to full seconds before formatting for consistency: + // getDurationText() should always display the same number + // as getDuration(DurationRounding::SECONDS) = getDurationInt() + duration = getDuration(DurationRounding::SECONDS); + } else { + duration = getDuration(DurationRounding::NONE); + } + return mixxx::Duration::formatSeconds(duration, precision); } QString Track::getTitle() const { diff --git a/src/track/track.h b/src/track/track.h index 351a80bfb05..3eda94af8ce 100644 --- a/src/track/track.h +++ b/src/track/track.h @@ -17,6 +17,7 @@ #include "track/playcounter.h" #include "track/trackmetadata.h" #include "util/sandbox.h" +#include "util/duration.h" #include "waveform/waveform.h" class Track; @@ -59,8 +60,10 @@ class Track : public QObject { Q_PROPERTY(double bpm READ getBpm WRITE setBpm) Q_PROPERTY(QString bpmFormatted READ getBpmText STORED false) Q_PROPERTY(QString key READ getKeyText WRITE setKeyText) - Q_PROPERTY(int duration READ getDuration WRITE setDuration) - Q_PROPERTY(QString durationFormatted READ getDurationText STORED false) + Q_PROPERTY(double duration READ getDuration WRITE setDuration) + Q_PROPERTY(QString durationFormatted READ getDurationTextSeconds STORED false) + Q_PROPERTY(QString durationFormattedCentiseconds READ getDurationTextCentiseconds STORED false) + Q_PROPERTY(QString durationFormattedMilliseconds READ getDurationTextMilliseconds STORED false) QFileInfo getFileInfo() const { // Copying a QFileInfo is thread-safe (implicit sharing), no locking needed. @@ -114,12 +117,27 @@ class Track : public QObject { // Returns the bitrate as a string QString getBitrateText() const; - // Set duration in seconds - void setDuration(int); - // Returns the duration in seconds - int getDuration() const; - // Returns the duration as a string: H:MM:SS - QString getDurationText() const; + void setDuration(double duration); + double getDuration() const { + return getDuration(DurationRounding::NONE); + } + // Returns the duration rounded to seconds + int getDurationInt() const { + return static_cast(getDuration(DurationRounding::SECONDS)); + } + // Returns the duration formatted as a string (H:MM:SS or H:MM:SS.cc or H:MM:SS.mmm) + QString getDurationText(mixxx::Duration::Precision precision) const; + + // Helper functions for Q_PROPERTYs + QString getDurationTextSeconds() const { + return getDurationText(mixxx::Duration::Precision::SECONDS); + } + QString getDurationTextCentiseconds() const { + return getDurationText(mixxx::Duration::Precision::CENTISECONDS); + } + QString getDurationTextMilliseconds() const { + return getDurationText(mixxx::Duration::Precision::MILLISECONDS); + } // Set BPM double setBpm(double); @@ -324,6 +342,12 @@ class Track : public QObject { // Only used by TrackDAO! void setId(TrackId id); + enum class DurationRounding { + SECONDS, // rounded to full seconds + NONE // unmodified + }; + double getDuration(DurationRounding rounding) const; + // The file const QFileInfo m_fileInfo; diff --git a/src/track/trackmetadata.cpp b/src/track/trackmetadata.cpp index 77d0f777079..5affb01425c 100644 --- a/src/track/trackmetadata.cpp +++ b/src/track/trackmetadata.cpp @@ -67,14 +67,19 @@ QString TrackMetadata::reformatYear(QString year) { } TrackMetadata::TrackMetadata() - : m_bitrate(0), + : m_duration(0.0), + m_bitrate(0), m_channels(0), - m_duration(0), m_sampleRate(0) { } bool operator==(const TrackMetadata& lhs, const TrackMetadata& rhs) { - return (lhs.getArtist() == rhs.getArtist()) && + // Compare the integer and double fields 1st for maximum efficiency + return (lhs.getBitrate() == rhs.getBitrate()) && + (lhs.getChannels() == rhs.getChannels()) && + (lhs.getSampleRate() == rhs.getSampleRate()) && + (lhs.getDuration() == rhs.getDuration()) && + (lhs.getArtist() == rhs.getArtist()) && (lhs.getTitle() == rhs.getTitle()) && (lhs.getAlbum() == rhs.getAlbum()) && (lhs.getAlbumArtist() == rhs.getAlbumArtist()) && @@ -86,10 +91,6 @@ bool operator==(const TrackMetadata& lhs, const TrackMetadata& rhs) { (lhs.getComposer() == rhs.getComposer()) && (lhs.getGrouping() == rhs.getGrouping()) && (lhs.getKey() == rhs.getKey()) && - (lhs.getChannels() == rhs.getChannels()) && - (lhs.getSampleRate() == rhs.getSampleRate()) && - (lhs.getBitrate() == rhs.getBitrate()) && - (lhs.getDuration() == rhs.getDuration()) && (lhs.getBpm() == rhs.getBpm()) && (lhs.getReplayGain() == rhs.getReplayGain()); } diff --git a/src/track/trackmetadata.h b/src/track/trackmetadata.h index d80cb0a6b1c..58cef6bb859 100644 --- a/src/track/trackmetadata.h +++ b/src/track/trackmetadata.h @@ -122,10 +122,10 @@ class TrackMetadata { } // #seconds - int getDuration() const { + double getDuration() const { return m_duration; } - void setDuration(int duration) { + void setDuration(double duration) { m_duration = duration; } @@ -189,10 +189,12 @@ class TrackMetadata { Bpm m_bpm; ReplayGain m_replayGain; + // Floating-point fields (in alphabetical order) + double m_duration; // seconds + // Integer fields (in alphabetical order) int m_bitrate; // kbit/s int m_channels; - int m_duration; // seconds int m_sampleRate; // Hz }; diff --git a/src/track/trackmetadatataglib.cpp b/src/track/trackmetadatataglib.cpp index 021190727a7..26a4eba9061 100644 --- a/src/track/trackmetadatataglib.cpp +++ b/src/track/trackmetadatataglib.cpp @@ -3,8 +3,13 @@ #include "track/tracknumbers.h" #include "util/assert.h" +#include "util/duration.h" #include "util/memory.h" +// TagLib has full support for MP4 atom types since version 1.8 +#define TAGLIB_HAS_MP4_ATOM_TYPES \ + (TAGLIB_MAJOR_VERSION > 1) || ((TAGLIB_MAJOR_VERSION == 1) && (TAGLIB_MINOR_VERSION >= 8)) + // TagLib has support for the Ogg Opus file format since version 1.9 #define TAGLIB_HAS_OPUSFILE \ ((TAGLIB_MAJOR_VERSION > 1) || ((TAGLIB_MAJOR_VERSION == 1) && (TAGLIB_MINOR_VERSION >= 9))) @@ -13,14 +18,14 @@ #define TAGLIB_HAS_WAV_ID3V2TAG \ (TAGLIB_MAJOR_VERSION > 1) || ((TAGLIB_MAJOR_VERSION == 1) && (TAGLIB_MINOR_VERSION >= 9)) -// TagLib has full support for MP4 atom types since version 1.8 -#define TAGLIB_HAS_MP4_ATOM_TYPES \ - (TAGLIB_MAJOR_VERSION > 1) || ((TAGLIB_MAJOR_VERSION == 1) && (TAGLIB_MINOR_VERSION >= 8)) - // TagLib has support for has() style functions since version 1.9 #define TAGLIB_HAS_TAG_CHECK \ (TAGLIB_MAJOR_VERSION > 1) || ((TAGLIB_MAJOR_VERSION == 1) && (TAGLIB_MINOR_VERSION >= 9)) +// TagLib has support for length in milliseconds since version 1.10 +#define TAGLIB_HAS_LENGTH_IN_MILLISECONDS \ + (TAGLIB_MAJOR_VERSION > 1) || ((TAGLIB_MAJOR_VERSION == 1) && (TAGLIB_MINOR_VERSION >= 10)) + #ifdef _WIN32 static_assert(sizeof(wchar_t) == sizeof(QChar), "wchar_t is not the same size than QChar"); #define TAGLIB_FILENAME_FROM_QSTRING(fileName) (const wchar_t*)fileName.utf16() @@ -250,10 +255,22 @@ void readAudioProperties(TrackMetadata* pTrackMetadata, const TagLib::AudioProperties& audioProperties) { DEBUG_ASSERT(pTrackMetadata); + // NOTE(uklotzde): All audio properties will be updated + // with the actual (and more precise) values when reading + // the audio data for this track. Often those properties + // stored in tags don't match with the corresponding + // audio data in the file. pTrackMetadata->setChannels(audioProperties.channels()); pTrackMetadata->setSampleRate(audioProperties.sampleRate()); - pTrackMetadata->setDuration(audioProperties.length()); pTrackMetadata->setBitrate(audioProperties.bitrate()); +#if TAGLIB_HAS_LENGTH_IN_MILLISECONDS + // Cast to double is required for duration with sub-second precision + const double dLengthInMilliseconds = audioProperties.lengthInMilliseconds(); + const double duration = dLengthInMilliseconds / mixxx::Duration::kMillisPerSecond; +#else + const double duration = audioProperties.length(); +#endif + pTrackMetadata->setDuration(duration); } bool readAudioProperties(TrackMetadata* pTrackMetadata, diff --git a/src/util/duration.cpp b/src/util/duration.cpp new file mode 100644 index 00000000000..96272d20553 --- /dev/null +++ b/src/util/duration.cpp @@ -0,0 +1,52 @@ +#include "util/duration.h" + +#include +#include +#include + +#include "util/assert.h" +#include + +namespace mixxx { + +namespace { + +static const qint64 kSecondsPerMinute = 60; +static const qint64 kSecondsPerHour = 60 * kSecondsPerMinute; +static const qint64 kSecondsPerDay = 24 * kSecondsPerHour; + +} // namespace + +// static +QString DurationBase::formatSeconds(double dSeconds, Precision precision) { + if (dSeconds < 0.0) { + // negative durations are not supported + return "?"; + } + + const qint64 days = static_cast(std::floor(dSeconds)) / kSecondsPerDay; + dSeconds -= days * kSecondsPerDay; + + // NOTE(uklotzde): QTime() constructs a 'null' object, but + // we need 'zero' here. + QTime t = QTime(0, 0).addMSecs(dSeconds * kMillisPerSecond); + + QString formatString = + (days > 0 ? (QString::number(days) % + QLatin1String("'d', ")) : QString()) % + QLatin1String(days > 0 || t.hour() > 0 ? "hh:mm:ss" : "mm:ss") % + QLatin1String(Precision::SECONDS == precision ? "" : ".zzz"); + + QString durationString = t.toString(formatString); + + // The format string gives us milliseconds but we want + // centiseconds. Slice one character off. + if (Precision::CENTISECONDS == precision) { + DEBUG_ASSERT(1 <= durationString.length()); + durationString = durationString.left(durationString.length() - 1); + } + + return durationString; +} + +} // namespace mixxx diff --git a/src/util/duration.h b/src/util/duration.h index 0c72a3612d5..4c375d60a4d 100644 --- a/src/util/duration.h +++ b/src/util/duration.h @@ -10,17 +10,9 @@ #include "util/assert.h" namespace mixxx { -namespace { - -const qint64 kMillisPerSecond = 1e3; -const qint64 kMicrosPerSecond = 1e6; -const qint64 kNanosPerSecond = 1e9; -const qint64 kNanosPerMilli = 1e6; -const qint64 kNanosPerMicro = 1e3; - -} // namespace class DurationBase { + public: enum Units { HEX, @@ -71,6 +63,24 @@ class DurationBase { return static_cast(m_durationNanos); } + enum class Precision { + SECONDS, + CENTISECONDS, + MILLISECONDS + }; + + // The standard way of formatting a floating-point duration in seconds. + // Used for display of track duration, etc. + static QString formatSeconds( + double dSeconds, + Precision precision = Precision::SECONDS); + + static const qint64 kMillisPerSecond = 1000; + static const qint64 kMicrosPerSecond = kMillisPerSecond * 1000; + static const qint64 kNanosPerSecond = kMicrosPerSecond * 1000; + static const qint64 kNanosPerMilli = kNanosPerSecond / 1000; + static const qint64 kNanosPerMicro = kNanosPerMilli / 1000; + protected: DurationBase(qint64 durationNanos) : m_durationNanos(durationNanos) { diff --git a/src/util/time.cpp b/src/util/time.cpp index 231a96527da..2cd563ab393 100644 --- a/src/util/time.cpp +++ b/src/util/time.cpp @@ -1,45 +1,10 @@ #include "util/time.h" -#include -#include -#include - -#include "util/assert.h" - // static LLTIMER Time::s_timer; + // static bool Time::s_testMode = false; -// static -mixxx::Duration Time::s_testElapsed = mixxx::Duration::fromNanos(0); // static -QString Time::formatSeconds(double dSeconds, Precision precision) { - if (dSeconds < 0) { - return "?"; - } - - const int days = static_cast(dSeconds) / kSecondsPerDay; - dSeconds -= days * kSecondsPerDay; - - // NOTE(uklotzde): Time() constructs a 'null' object, but - // we need 'zero' here. - QTime t = QTime(0, 0).addMSecs(dSeconds * kMillisPerSecond); - - QString formatString = - (days > 0 ? (QString::number(days) % - QLatin1String("'d', ")) : QString()) % - QLatin1String(days > 0 || t.hour() > 0 ? "hh:mm:ss" : "mm:ss") % - QLatin1String(Precision::SECONDS == precision ? "" : ".zzz"); - - QString timeString = t.toString(formatString); - - // The format string gives us milliseconds but we want - // centiseconds. Slice one character off. - if (Precision::CENTISECONDS == precision) { - DEBUG_ASSERT(1 <= timeString.length()); - timeString = timeString.left(timeString.length() - 1); - } - - return timeString; -} +mixxx::Duration Time::s_testElapsed = mixxx::Duration::fromNanos(0); diff --git a/src/util/time.h b/src/util/time.h index b6d04f74b27..6840c93d2a3 100644 --- a/src/util/time.h +++ b/src/util/time.h @@ -1,8 +1,6 @@ #ifndef UTIL_TIME_H #define UTIL_TIME_H -#include - #include "util/performancetimer.h" #include "util/threadcputimer.h" #include "util/timer.h" @@ -13,11 +11,6 @@ class Time { public: - static const int kMillisPerSecond = 1000; - static const int kSecondsPerMinute = 60; - static const int kSecondsPerHour = 60 * kSecondsPerMinute; - static const int kSecondsPerDay = 24 * kSecondsPerHour; - static void start() { s_timer.start(); } @@ -40,17 +33,6 @@ class Time { s_testElapsed = elapsed; } - enum class Precision { - SECONDS, - CENTISECONDS, - MILLISECONDS - }; - - // The standard way of formatting a time in seconds. Used for display - // of track duration, etc. - static QString formatSeconds(double dSeconds, - Precision precision = Time::Precision::SECONDS); - private: static LLTIMER s_timer; diff --git a/src/widget/wnumberpos.cpp b/src/widget/wnumberpos.cpp index d525b6f1cce..9280b6b4759 100644 --- a/src/widget/wnumberpos.cpp +++ b/src/widget/wnumberpos.cpp @@ -6,7 +6,7 @@ #include "control/controlobject.h" #include "control/controlproxy.h" #include "util/math.h" -#include "util/time.h" +#include "util/duration.h" WNumberPos::WNumberPos(const char* group, QWidget* parent) : WNumber(parent), @@ -77,25 +77,26 @@ void WNumberPos::setValue(double dValue) { void WNumberPos::slotSetValue(double dValue) { m_dOldValue = dValue; - double valueMillis = 0.0; + double dPosSeconds = 0.0; if (m_dTrackSamples > 0 && m_dTrackSampleRate > 0) { - double dDuration = m_dTrackSamples / m_dTrackSampleRate / 2.0; - valueMillis = dValue * 500.0 * m_dTrackSamples / m_dTrackSampleRate; - double durationMillis = dDuration * Time::kMillisPerSecond; + double dDurationSeconds = (m_dTrackSamples / 2.0) / m_dTrackSampleRate; + double dDurationMillis = dDurationSeconds * 1000.0; + double dPosMillis = dValue * dDurationMillis; if (m_bRemain) { - valueMillis = math_max(durationMillis - valueMillis, 0.0); + dPosMillis = math_max(dDurationMillis - dPosMillis, 0.0); } + dPosSeconds = dPosMillis / 1000.0; } - QString valueString; - if (valueMillis >= 0) { - valueString = m_skinText % Time::formatSeconds( - valueMillis / Time::kMillisPerSecond, Time::Precision::CENTISECONDS); + QString sPosText; + if (dPosSeconds >= 0.0) { + sPosText = m_skinText % mixxx::Duration::formatSeconds( + dPosSeconds, mixxx::Duration::Precision::CENTISECONDS); } else { - valueString = m_skinText % QLatin1String("-") % Time::formatSeconds( - -valueMillis / Time::kMillisPerSecond, Time::Precision::CENTISECONDS); + sPosText = m_skinText % QLatin1String("-") % mixxx::Duration::formatSeconds( + -dPosSeconds, mixxx::Duration::Precision::CENTISECONDS); } - setText(valueString); + setText(sPosText); } void WNumberPos::slotSetRemain(double remain) {