From a4ab9829d5ac9c3d716372fb1f09fab546060b7b Mon Sep 17 00:00:00 2001 From: emeric Date: Fri, 5 Jun 2026 13:28:39 +0200 Subject: [PATCH] Try not to suggest same audio tracks --- .../AudioSimilarityEngine.impl.hpp | 4 ++ .../NearDuplicateEmbeddingConstraint.hpp | 68 +++++++++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 src/libs/services/recommendation/impl/audio-similarity/NearDuplicateEmbeddingConstraint.hpp diff --git a/src/libs/services/recommendation/impl/audio-similarity/AudioSimilarityEngine.impl.hpp b/src/libs/services/recommendation/impl/audio-similarity/AudioSimilarityEngine.impl.hpp index 9939c201..63998b6d 100644 --- a/src/libs/services/recommendation/impl/audio-similarity/AudioSimilarityEngine.impl.hpp +++ b/src/libs/services/recommendation/impl/audio-similarity/AudioSimilarityEngine.impl.hpp @@ -49,6 +49,7 @@ #include "math/PrincipalComponents.hpp" #include "math/StatsAccumulator.hpp" +#include "NearDuplicateEmbeddingConstraint.hpp" #include "track-selection-constraints/DuplicateTrackConstraint.hpp" #include "track-selection-constraints/InterpolationFitConstraint.hpp" #include "track-selection-constraints/MaxDistanceConstraint.hpp" @@ -115,9 +116,11 @@ namespace lms::recommendation constexpr float smoothTransitionWeight{ 0.2F }; constexpr float sameReleaseWeight{ 0.5F }; constexpr float sameArtistWeight{ 0.5F }; + constexpr float nearDuplicateThreshold{ 0.01F }; _similarityEvaluator = {}; _similarityEvaluator.addHardConstraint(std::make_unique()); + _similarityEvaluator.addHardConstraint(std::make_unique>(_trackVectors, nearDuplicateThreshold)); _similarityEvaluator.addHardConstraint(std::make_unique(_trackDistanceThreshold)); _similarityEvaluator.addSoftConstraint(std::make_unique(), interpolationFitWeight); _similarityEvaluator.addSoftConstraint(std::make_unique(), smoothTransitionWeight); @@ -126,6 +129,7 @@ namespace lms::recommendation _pathEvaluator = {}; _pathEvaluator.addHardConstraint(std::make_unique()); + _pathEvaluator.addHardConstraint(std::make_unique>(_trackVectors, nearDuplicateThreshold)); _pathEvaluator.addSoftConstraint(std::make_unique(), interpolationFitWeight); _pathEvaluator.addSoftConstraint(std::make_unique(), smoothTransitionWeight); _pathEvaluator.addSoftConstraint(std::make_unique(_trackMetadata), sameReleaseWeight); diff --git a/src/libs/services/recommendation/impl/audio-similarity/NearDuplicateEmbeddingConstraint.hpp b/src/libs/services/recommendation/impl/audio-similarity/NearDuplicateEmbeddingConstraint.hpp new file mode 100644 index 00000000..4f1e6a56 --- /dev/null +++ b/src/libs/services/recommendation/impl/audio-similarity/NearDuplicateEmbeddingConstraint.hpp @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2026 Emeric Poupon + * + * This file is part of LMS. + * + * LMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LMS. If not, see . + */ + +#pragma once + +#include + +#include "database/objects/TrackId.hpp" +#include "math/NormalizedCosineDistance.hpp" +#include "math/Vector.hpp" + +#include "track-selection-constraints/ITrackCandidateHardConstraint.hpp" +#include "Types.hpp" + +namespace lms::recommendation +{ + // Rejects any candidate whose embedding is closer than 'threshold' to any already-selected + // track's embedding — catches the same recording appearing in multiple compilations. + template + class NearDuplicateEmbeddingConstraint : public ITrackCandidateHardConstraint + { + public: + using ReducedVector = math::Vector; + using TrackVectorMap = std::unordered_map; + + NearDuplicateEmbeddingConstraint(const TrackVectorMap& trackVectors, float threshold) + : _trackVectors{ trackVectors } + , _threshold{ threshold } + { + } + + bool rejects(const TrackCandidateContext& context) const override + { + const auto itCandidate{ _trackVectors.find(context.candidateTrackId) }; + if (itCandidate == _trackVectors.cend()) + return false; + + const math::NormalizedCosineDistance distFunc{ *itCandidate->second }; + for (const db::TrackId selectedId : context.selectedTracks) + { + const auto it{ _trackVectors.find(selectedId) }; + if (it != _trackVectors.cend() && distFunc(*it->second) < _threshold) + return true; + } + return false; + } + + private: + const TrackVectorMap& _trackVectors; + float _threshold; + }; +} // namespace lms::recommendation