1 /* ArtistMatchEvaluator.cpp */
2 /*
3 * Copyright (C) 2011-2020 Michael Lugmair
4 *
5 * This file is part of sayonara player
6 *
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 */
20 #include "ArtistMatchEvaluator.h"
21 #include "ArtistMatch.h"
22
23 #include "Database/Connector.h"
24 #include "Database/LibraryDatabase.h"
25
26 #include "Utils/Utils.h"
27 #include "Utils/Algorithm.h"
28 #include "Utils/RandomGenerator.h"
29 #include "Utils/MetaData/Artist.h"
30 #include "Utils/Library/SearchMode.h"
31
32 #include <QByteArray>
33 #include <QMap>
34
35 using DynamicPlayback::ArtistMatch;
36
37 namespace
38 {
39 using ArtistLookupMap = QMap<QString, QList<ArtistId>>;
40
41 class NameUnificator
42 {
43 private:
44 QMap<QString, QString> mNameMap;
45
46 public:
unifyString(const QString & artistName)47 QString unifyString(const QString& artistName)
48 {
49 if(mNameMap.contains(artistName))
50 {
51 return mNameMap[artistName];
52 }
53
54 const auto result =
55 Library::Utils::convertSearchstring(artistName,
56 (Library::CaseInsensitve |
57 Library::NoSpecialChars |
58 Library::NoDiacriticChars));
59
60 mNameMap[artistName] = result;
61 return result;
62 }
63 };
64
65 static NameUnificator nameUnificator;
66
generateQuality()67 ArtistMatch::Quality generateQuality()
68 {
69 const auto randomNumber = Util::randomNumber(0, 100);
70 if(randomNumber > 60) // [100 - 60[ = 40 %
71 {
72 return ArtistMatch::Quality::Excellent;
73 }
74
75 else if(randomNumber > 30) // [60 - 30[ = 30%
76 {
77 return ArtistMatch::Quality::VeryGood;
78 }
79
80 else if(randomNumber > 10) // [30 - 10[ = 20%
81 {
82 return ArtistMatch::Quality::Good;
83 }
84
85 else // [0 - 10[ = 10%
86 {
87 return ArtistMatch::Quality::Poor;
88 }
89 }
90
nextQuality(ArtistMatch::Quality quality)91 ArtistMatch::Quality nextQuality(ArtistMatch::Quality quality)
92 {
93 // Excellent -> Very Good -> Good -> Poor -> Excellent -> Very Good...
94 switch(quality)
95 {
96 case ArtistMatch::Quality::Excellent:
97 return ArtistMatch::Quality::VeryGood;
98 case ArtistMatch::Quality::VeryGood:
99 return ArtistMatch::Quality::Good;
100 case ArtistMatch::Quality::Good:
101 return ArtistMatch::Quality::Poor;
102 case ArtistMatch::Quality::Poor:
103 default:
104 return ArtistMatch::Quality::Excellent;
105 }
106 }
107
createArtistLookupMap(const ArtistList & artists)108 ArtistLookupMap createArtistLookupMap(const ArtistList& artists)
109 {
110 ArtistLookupMap lookupMap;
111
112 for(const auto& artist : artists)
113 {
114 const auto& transformedName = nameUnificator.unifyString(artist.name());
115 if(!lookupMap.contains(transformedName))
116 {
117 lookupMap.insert(transformedName, QList<ArtistId> {artist.id()});
118 }
119 else
120 {
121 lookupMap[transformedName].push_back(artist.id());
122 }
123 }
124
125 return lookupMap;
126 }
127
getAllAvailableArtistsInLibrary(LibraryId libraryId)128 ArtistLookupMap getAllAvailableArtistsInLibrary(LibraryId libraryId)
129 {
130 auto* db = DB::Connector::instance();
131 auto* libraryDatabase = db->libraryDatabase(libraryId, 0);
132
133 ArtistList artists;
134 libraryDatabase->getAllArtists(artists, false);
135
136 return createArtistLookupMap(artists);
137 }
138
139 QList<ArtistId>
filterAvailableArtists(const ArtistMatch & artistMatch,ArtistMatch::Quality quality,const ArtistLookupMap & lookupMap)140 filterAvailableArtists(const ArtistMatch& artistMatch, ArtistMatch::Quality quality, const ArtistLookupMap& lookupMap)
141 {
142 QList<ArtistId> artistIds;
143
144 const auto entries = artistMatch.get(quality);
145 for(const auto& entry : entries)
146 {
147 const auto name = nameUnificator.unifyString(entry.artist);
148 const auto artistIdList = lookupMap[name];
149
150 std::copy(artistIdList.begin(), artistIdList.end(), std::back_inserter(artistIds));
151 }
152
153 Util::Algorithm::shuffle(artistIds);
154
155 return artistIds;
156 }
157 }
158
159 QList<ArtistId>
evaluateArtistMatch(const ArtistMatch & artistMatch,LibraryId libraryId)160 DynamicPlayback::evaluateArtistMatch(const ArtistMatch& artistMatch, LibraryId libraryId)
161 {
162 if(!artistMatch.isValid())
163 {
164 return QList<ArtistId> {};
165 }
166
167 const auto lookupMap = getAllAvailableArtistsInLibrary(libraryId);
168
169 QList<ArtistId> artistIds;
170 const auto originalQuality = generateQuality();
171 auto quality = originalQuality;
172 do
173 {
174 auto artistIdsForQuality = filterAvailableArtists(artistMatch, quality, lookupMap);
175 std::move(artistIdsForQuality.begin(), artistIdsForQuality.end(), std::back_inserter(artistIds));
176
177 quality = nextQuality(quality);
178 } while(quality != originalQuality);
179
180 return artistIds;
181 }