diff options
Diffstat (limited to 'src')
60 files changed, 1659 insertions, 372 deletions
diff --git a/src/engine/controls/loopingcontrol.cpp b/src/engine/controls/loopingcontrol.cpp index 02f9e10fb4..245201df76 100644 --- a/src/engine/controls/loopingcontrol.cpp +++ b/src/engine/controls/loopingcontrol.cpp @@ -53,7 +53,7 @@ LoopingControl::LoopingControl(QString group, m_loopSamples.setValue(m_oldLoopSamples); m_currentSample.setValue(0.0); m_pActiveBeatLoop = NULL; - + m_pRateControl = NULL; //Create loop-in, loop-out, loop-exit, and reloop/exit ControlObjects m_pLoopInButton = new ControlPushButton(ConfigKey(group, "loop_in")); connect(m_pLoopInButton, &ControlObject::valueChanged, @@ -682,6 +682,10 @@ void LoopingControl::setLoopOutToCurrentPosition() { m_loopSamples.setValue(loopSamples); } +void LoopingControl::setRateControl(RateControl* rateControl) { + m_pRateControl = rateControl; +} + void LoopingControl::slotLoopOut(double pressed) { if (m_pTrack == nullptr) { return; @@ -1060,16 +1064,19 @@ void LoopingControl::slotBeatLoop(double beats, bool keepStartPoint, bool enable newloopSamples.start = currentSample; } } else { - // loop_in is set to the previous beat if quantize is on. The - // closest beat might be ahead of play position which would cause a seek. - // TODO: If in reverse, should probably choose nextBeat. + // loop_in is set to the closest beat if quantize is on and the loop size is >= 1 beat. + // The closest beat might be ahead of play position and will cause a catching loop. double prevBeat; double nextBeat; pBeats->findPrevNextBeats(currentSample, &prevBeat, &nextBeat); if (m_pQuantizeEnabled->toBool() && prevBeat != -1) { + double beatLength = nextBeat - prevBeat; + double loopLength = beatLength * beats; + + double closestBeat = pBeats->findClosestBeat(currentSample); if (beats >= 1.0) { - newloopSamples.start = prevBeat; + newloopSamples.start = closestBeat; } else { // In case of beat length less then 1 beat: // (| - beats, ^ - current track's position): @@ -1078,15 +1085,29 @@ void LoopingControl::slotBeatLoop(double beats, bool keepStartPoint, bool enable // // If we press 1/2 beatloop we want loop from 50% to 100%, // If I press 1/4 beatloop, we want loop from 50% to 75% etc - double beat_len = nextBeat - prevBeat; - double loops_per_beat = 1.0 / beats; - double beat_pos = currentSample - prevBeat; - int beat_frac = - static_cast<int>(floor((beat_pos / beat_len) * - loops_per_beat)); - newloopSamples.start = prevBeat + beat_len / loops_per_beat * beat_frac; + double samplesSinceLastBeat = currentSample - prevBeat; + + // find the previous beat fraction and check if the current position is closer to this or the next one + // place the new loop start to the closer one + double previousFractionBeat = prevBeat + floor(samplesSinceLastBeat / loopLength) * loopLength; + double samplesSinceLastFractionBeat = currentSample - previousFractionBeat; + + if (samplesSinceLastFractionBeat <= (loopLength / 2.0)) { + newloopSamples.start = previousFractionBeat; + } else { + newloopSamples.start = previousFractionBeat + loopLength; + } } + // If running reverse, move the loop one loop size to the left. + // Thus, the loops end will be closest to the current position + bool reverse = false; + if (m_pRateControl != NULL) { + reverse = m_pRateControl->isReverseButtonPressed(); + } + if (reverse) { + newloopSamples.start -= loopLength; + } } else { newloopSamples.start = currentSample; } diff --git a/src/engine/controls/loopingcontrol.h b/src/engine/controls/loopingcontrol.h index 55d4f0c15f..6be5fdb78b 100644 --- a/src/engine/controls/loopingcontrol.h +++ b/src/engine/controls/loopingcontrol.h @@ -8,11 +8,12 @@ #include <QObject> #include <QStack> -#include "preferences/usersettings.h" +#include "control/controlvalue.h" #include "engine/controls/enginecontrol.h" -#include "track/track.h" +#include "engine/controls/ratecontrol.h" +#include "preferences/usersettings.h" #include "track/beats.h" -#include "control/controlvalue.h" +#include "track/track.h" #define MINIMUM_AUDIBLE_LOOP_SIZE 300 // In samples @@ -50,7 +51,7 @@ class LoopingControl : public EngineControl { double getSyncPositionInsideLoop(double dRequestedPlaypos, double dSyncedPlayPos); void notifySeek(double dNewPlaypos) override; - + void setRateControl(RateControl* rateControl); bool isLoopingEnabled(); public slots: @@ -129,6 +130,7 @@ class LoopingControl : public EngineControl { ControlPushButton* m_pLoopHalveButton; ControlPushButton* m_pLoopDoubleButton; ControlObject* m_pSlipEnabled; + RateControl* m_pRateControl; ControlObject* m_pPlayButton; bool m_bLoopingEnabled; diff --git a/src/engine/controls/ratecontrol.cpp b/src/engine/controls/ratecontrol.cpp index 75c7327b10..5ee5af58ff 100644 --- a/src/engine/controls/ratecontrol.cpp +++ b/src/engine/controls/ratecontrol.cpp @@ -556,3 +556,10 @@ void RateControl::resetRateTemp(void) void RateControl::notifySeek(double playPos) { m_pScratchController->notifySeek(playPos); } + +bool RateControl::isReverseButtonPressed() { + if (m_pReverseButton) { + return m_pReverseButton->toBool(); + } + return false; +} diff --git a/src/engine/controls/ratecontrol.h b/src/engine/controls/ratecontrol.h index 635ae15b2e..a53dfd383f 100644 --- a/src/engine/controls/ratecontrol.h +++ b/src/engine/controls/ratecontrol.h @@ -78,6 +78,7 @@ public: static void setRateRampSensitivity(int); static int getRateRampSensitivity(); void notifySeek(double dNewPlaypos) override; + bool isReverseButtonPressed(); public slots: void slotReverseRollActivate(double); diff --git a/src/engine/enginebuffer.cpp b/src/engine/enginebuffer.cpp index 2c10c3bfe8..5ba47c24a5 100644 --- a/src/engine/enginebuffer.cpp +++ b/src/engine/enginebuffer.cpp @@ -180,14 +180,13 @@ EngineBuffer::EngineBuffer(const QString& group, UserSettingsPointer pConfig, // quantization (alignment) of loop in/out positions and (hot)cues with // beats. QuantizeControl* quantize_control = new QuantizeControl(group, pConfig); + addControl(quantize_control); + m_pQuantize = ControlObject::getControl(ConfigKey(group, "quantize")); // Create the Loop Controller m_pLoopingControl = new LoopingControl(group, pConfig); addControl(m_pLoopingControl); - addControl(quantize_control); - m_pQuantize = ControlObject::getControl(ConfigKey(group, "quantize")); - m_pEngineSync = pMixingEngine->getEngineSync(); m_pSyncControl = new SyncControl(group, pConfig, pChannel, m_pEngineSync); @@ -198,9 +197,12 @@ EngineBuffer::EngineBuffer(const QString& group, UserSettingsPointer pConfig, addControl(m_pVinylControlControl); #endif + // Create the Rate Controller m_pRateControl = new RateControl(group, pConfig); // Add the Rate Controller addControl(m_pRateControl); + // Looping Control needs Rate Control for Reverse Button + m_pLoopingControl->setRateControl(m_pRateControl); // Create the BPM Controller m_pBpmControl = new BpmControl(group, pConfig); @@ -532,6 +534,10 @@ TrackPointer EngineBuffer::getLoadedTrack() const { return m_pCurrentTrack; } +bool EngineBuffer::isReverse() { + return m_reverse_old; +} + void EngineBuffer::ejectTrack() { // clear track values in any case, this may fix Bug #1450424 //qDebug() << "EngineBuffer::ejectTrack()"; diff --git a/src/engine/enginebuffer.h b/src/engine/enginebuffer.h index 46735d16fa..394794225f 100644 --- a/src/engine/enginebuffer.h +++ b/src/engine/enginebuffer.h @@ -146,6 +146,8 @@ class EngineBuffer : public EngineObject { bool getQueuedSeekPosition(double* pSeekPosition); TrackPointer getLoadedTrack() const; + bool isReverse(); + double getExactPlayPos(); double getVisualPlayPos(); double getTrackSamples(); diff --git a/src/library/dao/directorydao.cpp b/src/library/dao/directorydao.cpp index c490ef959d..15338680c7 100644 --- a/src/library/dao/directorydao.cpp +++ b/src/library/dao/directorydao.cpp @@ -14,7 +14,6 @@ int DirectoryDAO::addDirectory(const QString& newDir) { // Do nothing if the dir to add is a child of a directory that is already in // the db. - ScopedTransaction transaction(m_database); QStringList dirs = getDirs(); QString childDir; QString parentDir; @@ -46,7 +45,6 @@ int DirectoryDAO::addDirectory(const QString& newDir) { LOG_FAILED_QUERY(query) << "Adding new dir (" % newDir % ") failed."; return SQL_ERROR; } - transaction.commit(); return ALL_FINE; } @@ -80,13 +78,12 @@ int DirectoryDAO::removeDirectory(const QString& dir) { } -QSet<TrackId> DirectoryDAO::relocateDirectory(const QString& oldFolder, +QList<TrackRef> DirectoryDAO::relocateDirectory(const QString& oldFolder, const QString& newFolder) { // TODO(rryan): This method could use error reporting. It can fail in // mysterious ways for example if a track in the oldFolder also has a zombie // track location in newFolder then the replace query will fail because the // location column becomes non-unique. - ScopedTransaction transaction(m_database); QSqlQuery query(m_database); query.prepare("UPDATE " % DIRECTORYDAO_TABLE % " SET " % DIRECTORYDAO_DIR % "=:newFolder WHERE " % DIRECTORYDAO_DIR % " = :oldFolder"); @@ -95,7 +92,7 @@ QSet<TrackId> DirectoryDAO::relocateDirectory(const QString& oldFolder, if (!query.exec()) { LOG_FAILED_QUERY(query) << "could not relocate directory" << oldFolder << "to" << newFolder; - return QSet<TrackId>(); + return {}; } // on Windows the absolute path starts with the drive name @@ -113,35 +110,32 @@ QSet<TrackId> DirectoryDAO::relocateDirectory(const QString& oldFolder, .arg(startsWithOldFolder, kSqlLikeMatchAll)); if (!query.exec()) { LOG_FAILED_QUERY(query) << "could not relocate path of tracks"; - return QSet<TrackId>(); + return {}; } - QSet<TrackId> trackIds; - QList<int> loc_ids; - QStringList old_locs; + QList<DbId> loc_ids; + QList<TrackRef> trackRefs; while (query.next()) { - trackIds.insert(TrackId(query.value(0))); - loc_ids.append(query.value(1).toInt()); - old_locs.append(query.value(2).toString()); + loc_ids.append(DbId(query.value(1).toInt())); + trackRefs.append(TrackRef::fromFileInfo(query.value(2).toString(), TrackId(query.value(0)))); } QString replacement = "UPDATE track_locations SET location = :newloc " "WHERE id = :id"; query.prepare(replacement); for (int i = 0; i < loc_ids.size(); ++i) { - QString newloc = old_locs.at(i); + QString newloc = trackRefs.at(i).getLocation(); newloc.replace(0, oldFolder.size(), newFolder); query.bindValue("newloc", newloc); - query.bindValue("id", loc_ids.at(i)); + query.bindValue("id", loc_ids.at(i).toVariant()); if (!query.exec()) { LOG_FAILED_QUERY(query) << "could not relocate path of tracks"; - return QSet<TrackId>(); + return {}; } } - qDebug() << "Relocated tracks:" << trackIds.size(); - transaction.commit(); - return trackIds; + qDebug() << "Relocated tracks:" << trackRefs.size(); + return trackRefs; } QStringList DirectoryDAO::getDirs() { diff --git a/src/library/dao/directorydao.h b/src/library/dao/directorydao.h index 827bd4a703..6892021a96 100644 --- a/src/library/dao/directorydao.h +++ b/src/library/dao/directorydao.h @@ -21,7 +21,7 @@ class DirectoryDAO : public DAO { int addDirectory(const QString& dir); int removeDirectory(const QString& dir); - QSet<TrackId> relocateDirectory(const QString& oldFolder, const QString& newFolder); + QList<TrackRef> relocateDirectory(const QString& oldFolder, const QString& newFolder); QStringList getDirs(); private: diff --git a/src/library/dao/trackdao.cpp b/src/library/dao/trackdao.cpp index ba08954a3c..a3e4d50d99 100644 --- a/src/library/dao/trackdao.cpp +++ b/src/library/dao/trackdao.cpp @@ -114,7 +114,7 @@ void TrackDAO::finish() { @return the track id for the track located at location, or -1 if the track is not in the database. */ -TrackId TrackDAO::getTrackId(const QString& absoluteFilePath) { +TrackId TrackDAO::getTrackId(const QString& absoluteFilePath) const { TrackId trackId; @@ -206,6 +206,37 @@ QString TrackDAO::getTrackLocation(TrackId trackId) { return trackLocation; } +QStringList TrackDAO::getTrackLocations(const QList<TrackId>& ids) { + QString stmt = + "SELECT track_locations.location FROM track_locations " + "INNER JOIN library on library.location=track_locations.id " + "WHERE library.id IN (%1)"; + { + QStringList idList; + idList.reserve(ids.size()); + for (const auto& id : ids) { + idList.append(id.toString()); + } + stmt = stmt.arg(idList.join(",")); + } + FwdSqlQuery query(m_database, stmt); + VERIFY_OR_DEBUG_ASSERT(!query.hasError()) { + return QStringList(); + } + if (!query.execPrepared()) { + return QStringList(); + } + QStringList locations; + locations.reserve(ids.size()); + const int locationColumn = query.record().indexOf("location"); + DEBUG_ASSERT(locationColumn >= 0); + while (query.next()) { + locations.append(query.fieldValue(locationColumn).toString()); + } + DEBUG_ASSERT(locations.size() <= ids.size()); + return locations; +} + void TrackDAO::saveTrack(Track* pTrack) { DEBUG_ASSERT(pTrack); if (pTrack->isDirty()) { @@ -268,18 +299,46 @@ void TrackDAO::slotTrackClean(Track* pTrack) { } void TrackDAO::databaseTrackAdded(TrackPointer pTrack) { - emit(dbTrackAdded(pTrack)); -} - -void TrackDAO::databaseTracksMoved(QSet<TrackId> tracksMovedSetOld, QSet<TrackId> tracksMovedSetNew) { - emit(tracksRemoved(tracksMovedSetNew)); - // results in a call of BaseTrackCache::updateTracksInIndex(trackIds); - emit(tracksAdded(tracksMovedSetOld)); + DEBUG_ASSERT(pTrack); + emit dbTrackAdded(pTrack); } void TrackDAO::databaseTracksChanged(QSet<TrackId> tracksChanged) { // results in a call of BaseTrackCache::updateTracksInIndex(trackIds); - emit(tracksAdded(tracksChanged)); + if (!tracksChanged.isEmpty()) { + emit tracksAdded(tracksChanged); + } +} + +void TrackDAO::databaseTracksReplaced(QList<QPair<TrackRef, TrackRef>> replacedTracks) { + QSet<TrackId> removedTrackIds; + QSet<TrackId> changedTrackIds; + for (const auto& replacedTrack : replacedTracks) { + const auto& removedTrackRef = replacedTrack.first; + const auto& changedTrackRef = replacedTrack.second; + DEBUG_ASSERT(removedTrackRef.getId().isValid()); + DEBUG_ASSERT(changedTrackRef.getId().isValid()); + // The (old)) location of the (re)moved track must be known! + DEBUG_ASSERT(!removedTrackRef.getLocation().isEmpty()); + // The (new) location of the changed track might be empty. + DEBUG_ASSERT(removedTrackRef.getLocation() != changedTrackRef.getLocation()); + changedTrackIds.insert(changedTrackRef.getId()); + // The ids might be identical if the same track has been only been + // relocated. In this case the track has not been removed. + if (removedTrackRef.getId() != changedTrackRef.getId()) { + // The id must also not match with any other changed track! + DEBUG_ASSERT(!changedTrackIds.contains(removedTrackRef.getId())); + removedTrackIds.insert(removedTrackRef.getId()); + } + } + DEBUG_ASSERT(removedTrackIds.size() <= changedTrackIds.size()); +#if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0) + DEBUG_ASSERT(!removedTrackIds.intersects(changedTrackIds)); +#endif + if (!removedTrackIds.isEmpty()) { + emit tracksRemoved(removedTrackIds); + } + databaseTracksChanged(changedTrackIds); } void TrackDAO::slotTrackChanged(Track* pTrack) { @@ -828,11 +887,11 @@ void TrackDAO::afterUnhidingTracks( emit(tracksAdded(QSet<TrackId>::fromList(trackIds))); } -QList<TrackId> TrackDAO::getTrackIds(const QDir& dir) { +QList<TrackId> TrackDAO::getAllTrackIds(const QDir& rootDir) { // Capture entries that start with the directory prefix dir. // dir needs to end in a slash otherwise we might match other // directories. - const QString dirPath = dir.absolutePath(); + const QString dirPath = rootDir.absolutePath(); QString likeClause = SqlLikeWildcardEscaper::apply(dirPath + "/", kSqlLikeMatchAll) + kSqlLikeMatchAll; QSqlQuery query(m_database); @@ -1566,8 +1625,7 @@ namespace { // moved instead of being deleted outright, and so we can salvage your // existing metadata that you have in your DB (like cue points, etc.). // returns falls if canceled -bool TrackDAO::detectMovedTracks(QSet<TrackId>* pTracksMovedSetOld, - QSet<TrackId>* pTracksMovedSetNew, +bool TrackDAO::detectMovedTracks(QList<QPair<TrackRef, TrackRef>>* pReplacedTracks, const QStringList& addedTracks, volatile const bool* pCancel) { // This function should not start a transaction on it's own! @@ -1722,10 +1780,11 @@ bool TrackDAO::detectMovedTracks(QSet<TrackId>* pTracksMovedSetOld, } } - // We collect all the old tracks that has to be updated in BaseTrackCache - pTracksMovedSetOld->insert(oldTrackId); - // We collect collect all the new tracks the where added and deleted to BaseTrackCache - pTracksMovedSetNew->insert(newTrackId); + if (pReplacedTracks) { + auto oldTrackRef = TrackRef::fromFileInfo(oldTrackLocation, oldTrackId); + auto newTrackRef = TrackRef::fromFileInfo(newTrackLocation, newTrackId); + pReplacedTracks->append(qMakePair(oldTrackRef, newTrackRef)); + } } return true; } diff --git a/src/library/dao/trackdao.h b/src/library/dao/trackdao.h index c27c41e312..2d4bc8bb21 100644 --- a/src/library/dao/trackdao.h +++ b/src/library/dao/trackdao.h @@ -39,9 +39,9 @@ class TrackDAO : public QObject, public virtual DAO, public virtual GlobalTrackC } void finish(); - TrackId getTrackId(const QString& absoluteFilePath); + TrackId getTrackId(const QString& absoluteFilePath) const; QList<TrackId> getTrackIds(const QList<QFileInfo>& files); - QList<TrackId> getTrackIds(const QDir& dir); + QList<TrackId> getAllTrackIds(const QDir& rootDir); // WARNING: Only call this from the main thread instance of TrackDAO. TrackPointer getTrack(TrackId trackId) const; @@ -49,6 +49,7 @@ class TrackDAO : public QObject, public virtual DAO, public virtual GlobalTrackC // Returns a set of all track locations in the library. QSet<QString> getTrackLocations(); QString getTrackLocation(TrackId trackId); + QStringList getTrackLocations(const QList<TrackId>& trackIds); TrackPointer addSingleTrack(const TrackFile& trackFile, bool unremove); QList<TrackId> addMultipleTracks(const QList<QFileInfo>& fileInfoList, bool unremove); @@ -90,8 +91,7 @@ class TrackDAO : public QObject, public virtual DAO, public virtual GlobalTrackC void markTracksInDirectoriesAsVerified(const QStringList& directories); void invalidateTrackLocationsInLibrary(); void markUnverifiedTracksAsDeleted(); - bool detectMovedTracks(QSet<TrackId>* pTracksMovedSetOld, - QSet<TrackId>* pTracksMovedSetNew, + bool detectMovedTracks(QList<QPair<TrackRef, TrackRef>>* pReplacedTracks, |