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 }