/// <summary> /// Find Similar Tracks using passed audio samples as input and return a List /// </summary> /// <param name="lshHashTables">Number of hash tables from the database</param> /// <param name="lshGroupsPerKey">Number of groups per hash table</param> /// <param name="thresholdTables">Minimum number of hash tables that must be found for one signature to be considered a candidate (0 and 1 = return all candidates, 2+ = return only exact matches)</param> /// <param name="param">Audio File Work Unit Parameter Object</param> /// <param name="optimizeSignatureCount">Reduce the number of signatures in order to increase the search performance</param> /// <param name="doSearchEverything">disregard the local sensitivity hashes and search the whole database</param> /// <param name="splashScreen">The "please wait" splash screen (or null)</param> /// <returns>a list of perceptually similar tracks</returns> public List <FindSimilar.QueryResult> FindSimilarFromAudioSamplesList( int lshHashTables, int lshGroupsPerKey, int thresholdTables, WorkUnitParameterObject param, bool optimizeSignatureCount, bool doSearchEverything, SplashSceenWaitingForm splashScreen) { DbgTimer t = new DbgTimer(); t.Start(); if (splashScreen != null) { splashScreen.SetProgress(2, "Creating fingerprints from audio samples ..."); } // Get fingerprints double[][] logSpectrogram; List <bool[]> fingerprints = fingerprintService.CreateFingerprintsFromAudioSamples(param.AudioSamples, param, out logSpectrogram); #if DEBUG // Save debug images using fingerprinting methods //Analyzer.SaveFingerprintingDebugImages(param.FileName, logSpectrogram, fingerprints, fingerprintService, param.FingerprintingConfiguration); #endif if (splashScreen != null) { splashScreen.SetProgress(3, String.Format("Successfully created {0} fingerprints.", fingerprints.Count)); } // If the number of signatures is to big, only keep the first MAX_SIGNATURE_COUNT to avoid a very time consuming search if (optimizeSignatureCount && fingerprints.Count > MAX_SIGNATURE_COUNT) { if (splashScreen != null) { splashScreen.SetProgress(4, String.Format("Only using the first {0} fingerprints out of {1}.", MAX_SIGNATURE_COUNT, fingerprints.Count)); } fingerprints.RemoveRange(MAX_SIGNATURE_COUNT, fingerprints.Count - MAX_SIGNATURE_COUNT); Dbg.WriteLine("Only using the first {0} fingerprints.", MAX_SIGNATURE_COUNT); } long elapsedMiliseconds = 0; // Query the database using Min Hash Dictionary <int, QueryStats> allCandidates = QueryFingerprintManager.QueryOneSongMinHash( fingerprints, dbService, minHash, lshHashTables, lshGroupsPerKey, thresholdTables, ref elapsedMiliseconds, doSearchEverything, splashScreen); if (splashScreen != null) { splashScreen.SetProgress(91, String.Format("Found {0} candidates.", allCandidates.Count)); } IEnumerable <int> ids = allCandidates.Select(p => p.Key); IList <Track> tracks = dbService.ReadTrackById(ids); if (splashScreen != null) { splashScreen.SetProgress(95, String.Format("Reading {0} tracks.", tracks.Count)); } // Order by Hamming Similarity // TODO: What does the 0.4 number do here? // there doesn't seem to be any change using another number?! /* * // Using PLINQ * IOrderedEnumerable<KeyValuePair<int, QueryStats>> order = allCandidates * .OrderBy((pair) => pair.Value.OrderingValue = * pair.Value.HammingDistance / pair.Value.NumberOfTotalTableVotes + 0.4 * pair.Value.MinHammingDistance); + + + var fingerprintList = (from o in order + join track in tracks on o.Key equals track.Id + select new FindSimilar.QueryResult { + Id = track.Id, + Path = track.FilePath, + Duration = track.TrackLengthMs, + Similarity = o.Value.Similarity + }).ToList(); */ // http://msdn.microsoft.com/en-us/library/dd460719(v=vs.110).aspx // http://stackoverflow.com/questions/2767709/c-sharp-joins-where-with-linq-and-lambda // Lambda query to order the candidates var order = allCandidates.AsParallel() .OrderBy((pair) => pair.Value.OrderingValue = pair.Value.HammingDistance / pair.Value.NumberOfTotalTableVotes + 0.4 * pair.Value.MinHammingDistance) .Take(200); // TODO: Be able to create the above query as a LINQ query // Join on the ID properties. var fingerprintList = (from o in order.AsParallel() join track in tracks.AsParallel() on o.Key equals track.Id select new FindSimilar.QueryResult { Id = track.Id, Path = track.FilePath, Duration = track.TrackLengthMs, Similarity = o.Value.Similarity }) .OrderByDescending((ord) => ord.Similarity) .ToList(); if (splashScreen != null) { splashScreen.SetProgress(100, "Ready!"); } Dbg.WriteLine("FindSimilarFromAudioSamplesList - Total Execution Time: {0} ms", t.Stop().TotalMilliseconds); return(fingerprintList); }
/// <summary> /// Query one specific song using MinHash algorithm. /// </summary> /// <param name="signatures">Signature signatures from a song</param> /// <param name="dbService">DatabaseService used to query the underlying database</param> /// <param name="lshHashTables">Number of hash tables from the database</param> /// <param name="lshGroupsPerKey">Number of groups per hash table</param> /// <param name="thresholdTables">Minimum number of hash tables that must be found for one signature to be considered a candidate (0 = return all candidates, 2+ = return only exact matches)</param> /// <param name="queryTime">Set by the method, representing the query length</param> /// <param name="doSearchEverything">disregard the local sensitivity hashes and search the whole database</param> /// <param name="splashScreen">The "please wait" splash screen (or null)</param> /// <returns>Dictionary with Tracks ID's and the Query Statistics</returns> public static Dictionary <Int32, QueryStats> QueryOneSongMinHash( IEnumerable <bool[]> signatures, DatabaseService dbService, MinHash minHash, int lshHashTables, int lshGroupsPerKey, int thresholdTables, ref long queryTime, bool doSearchEverything = false, SplashSceenWaitingForm splashScreen = null) { Stopwatch stopWatch = new Stopwatch(); stopWatch.Start(); int signatureCounter = 0; int signatureTotalCount = signatures.Count(); Dictionary <int, QueryStats> stats = new Dictionary <int, QueryStats>(); foreach (bool[] signature in signatures) { #region Please Wait Splash Screen Cancel Event // check if the user clicked cancel if (splashScreen.CancellationPending) { break; } #endregion if (signature == null) { continue; } IDictionary <int, IList <HashBinMinHash> > candidates = null; if (doSearchEverything) { candidates = dbService.ReadAllFingerprints(); } else { // Compute Min Hash on randomly selected fingerprint int[] bin = minHash.ComputeMinHashSignature(signature); // Find all hashbuckets to care about Dictionary <int, long> hashes = minHash.GroupMinHashToLSHBuckets(bin, lshHashTables, lshGroupsPerKey); long[] hashbuckets = hashes.Values.ToArray(); // Find all candidates by querying the database for those hashbuckets candidates = dbService.ReadFingerprintsByHashBucketLsh(hashbuckets); } // Reduce the potential candidates list if the number of hash tables found for each signature are less than the threshold Dictionary <int, IList <HashBinMinHash> > potentialCandidates = SelectPotentialMatchesOutOfEntireDataset(candidates, thresholdTables); // get the final candidate list by only using the potential candidate list if (potentialCandidates.Count > 0) { IList <Fingerprint> fingerprints = dbService.ReadFingerprintById(potentialCandidates.Keys); Dictionary <Fingerprint, int> finalCandidates = fingerprints.ToDictionary(finger => finger, finger => potentialCandidates[finger.Id].Count); ArrangeCandidatesAccordingToFingerprints(signature, finalCandidates, lshHashTables, lshGroupsPerKey, stats); } #region Please Wait Splash Screen Update // calculate a percentage between 5 and 90 int percentage = (int)((float)(signatureCounter) / (float)signatureTotalCount * 85) + 5; if (splashScreen != null) { splashScreen.SetProgress(percentage, String.Format("Searching for similar fingerprints.\n(Signature {0} of {1})", signatureCounter + 1, signatureTotalCount)); } signatureCounter++; #endregion Updat } stopWatch.Stop(); queryTime = stopWatch.ElapsedMilliseconds; /*Set the query Time parameter*/ return(stats); }