private void CalculateSubFingerprint(float[] energyBands, float[] previousEnergyBands, List <SubFingerprint> list) { SubFingerprintHash hash = new SubFingerprintHash(); Dictionary <int, float> bitReliability = new Dictionary <int, float>(); List <SubFingerprint> subFingerprints = new List <SubFingerprint>(1 + flipWeakestBits); for (int m = 0; m < 32; m++) { float difference = energyBands[m] - energyBands[m + 1] - (previousEnergyBands[m] - previousEnergyBands[m + 1]); hash[m] = difference > 0; bitReliability.Add(m, difference > 0 ? difference : -difference); // take absolute value as reliability weight } list.Add(new SubFingerprint(index, hash, false)); if (flipWeakestBits > 0) { // calculate probable hashes by flipping the most unreliable bits (the bits with the least energy differences) List <int> weakestBits = new List <int>(bitReliability.Keys.OrderBy(key => bitReliability[key])); // generate hashes with all possible bit combinations flipped int variations = 1 << flipWeakestBits; for (int i = 1; i < variations; i++) // start at 1 since i0 equals to the original hash { SubFingerprintHash flippedHash = new SubFingerprintHash(hash.Value); for (int j = 0; j < flipWeakestBits; j++) { if (((i >> j) & 1) == 1) { flippedHash[weakestBits[j]] = !flippedHash[weakestBits[j]]; } } list.Add(new SubFingerprint(index, flippedHash, true)); } } }
private void btnFindMatches_Click(object sender, RoutedEventArgs e) { if (trackFingerprintListBox.SelectedItems.Count > 0) { SubFingerprintHash hash = (SubFingerprintHash)trackFingerprintListBox.SelectedItem; var matches = store.FindMatches(hash); ListMatches(matches); } }
private void trackFingerprintListBox_SelectionChanged(object sender, SelectionChangedEventArgs e) { fingerprintMatchListBox.Items.Clear(); if (trackFingerprintListBox.SelectedItems.Count > 0) { SubFingerprintHash hash = (SubFingerprintHash)trackFingerprintListBox.SelectedItem; foreach (SubFingerprintLookupEntry lookupEntry in store.CollisionMap.GetValues(hash)) { fingerprintMatchListBox.Items.Add(lookupEntry); } } }
public List <Match> FindMatches(SubFingerprintHash hash) { List <Match> matches = new List <Match>(); List <SubFingerprintLookupEntry> entries = collisionMap.GetValues(hash); //Debug.WriteLine("Finding matches..."); // compare each track with each other int cycle = 1; for (int x = 0; x < entries.Count; x++) { SubFingerprintLookupEntry entry1 = entries[x]; for (int y = cycle; y < entries.Count; y++) { SubFingerprintLookupEntry entry2 = entries[y]; if (entry1.AudioTrack != entry2.AudioTrack) // don't compare tracks with themselves //Debug.WriteLine("Comparing " + entry1.AudioTrack.Name + " with " + entry2.AudioTrack.Name + ":"); { if (store[entry1.AudioTrack].Count - entry1.Index < fingerprintSize || store[entry2.AudioTrack].Count - entry2.Index < fingerprintSize) { // the end of at least one track has been reached and there are not enough hashes left // to do a fingerprint comparison continue; } // sum up the bit errors List <SubFingerprintHash> track1Hashes = store[entry1.AudioTrack]; List <SubFingerprintHash> track2Hashes = store[entry2.AudioTrack]; uint bitErrors = 0; for (int s = 0; s < fingerprintSize; s++) { SubFingerprintHash track1Hash = track1Hashes[entry1.Index + s]; SubFingerprintHash track2Hash = track2Hashes[entry2.Index + s]; if (track1Hash.Value == 0 || track2Hash.Value == 0) { bitErrors = (uint)fingerprintSize * 32; break; } // skip fingerprints with hashes that are zero, since it is probably from // a track section with silence // by setting the bitErrors to the maximum, the match will not be added bitErrors += track1Hash.HammingDistance(track2Hash); } float bitErrorRate = bitErrors / (float)(fingerprintSize * 32); // sub-fingerprints * 32 bits //Debug.WriteLine("BER: " + bitErrorRate + " <- " + (bitErrorRate < threshold ? "MATCH!!!" : "no match")); if (bitErrorRate < threshold) { matches.Add(new Match { Similarity = 1 - bitErrorRate, Track1 = entry1.AudioTrack, Track1Time = SubFingerprintIndexToTimeSpan(entry1.Index), Track2 = entry2.AudioTrack, Track2Time = SubFingerprintIndexToTimeSpan(entry2.Index), Source = matchSourceName }); } } } cycle++; } //Debug.WriteLine("finished"); return(matches); }
public List <Match> FindMatches(SubFingerprintHash hash) { List <Match> matches = new List <Match>(); List <SubFingerprintLookupEntry> entries = collisionMap.GetValues(hash); for (int x = 0; x < entries.Count; x++) { SubFingerprintLookupEntry entry1 = entries[x]; for (int y = x; y < entries.Count; y++) { SubFingerprintLookupEntry entry2 = entries[y]; if (entry1.AudioTrack != entry2.AudioTrack) // don't compare tracks with themselves { var store1 = store[entry1.AudioTrack]; var store2 = store[entry2.AudioTrack]; List <SubFingerprintHash> hashes1 = store1.hashes; List <SubFingerprintHash> hashes2 = store2.hashes; int index1 = entry1.Index; int index2 = entry2.Index; TrackStore.IndexEntry indexEntry1, indexEntry2; int numTried = 0; // count of hashes tried to match int numMatched = 0; // count of hashes matched int frameCount = 0; // count over how many actual frames hashes were matched (an index in the store is the equivalent of a frame in the generator) bool matchFound = false; // Iterate through sequential frames TrackStore.IndexEntry indexEntryNone = new TrackStore.IndexEntry(); while (true) { indexEntry1 = store1.index.ContainsKey(index1) ? store1.index[index1] : indexEntryNone; indexEntry2 = store2.index.ContainsKey(index2) ? store2.index[index2] : indexEntryNone; // Hash collision // The union of the two ranges is the total number of distinct hashes // The intersection of the two ranges is the total number of similar hashes // NOTE The following block calculates the number of matches (the intersection) // of the two hash lists with the Zipper intersection algorithm and relies // on the hash list sorting in the fingerprint generator. // Other approaches tried which are slower: // - n*m element by element comparison (seven though the amount of elements is reasonably small) // - concatenating the two ranges (LINQ), sorting them, and linearly iterating over, counting the duplicates (sort is slow) // - using a hash set for collision detection (hash set insertion and lookup are costly) int i = indexEntry1.index; int i_e = indexEntry1.index + indexEntry1.length; int j = indexEntry2.index; int j_e = indexEntry2.index + indexEntry2.length; int intersectionCount = 0; // Count intersecting hashes of a frame with the Zipper algorithm while (i < i_e && j < j_e) { if (hashes1[i] < hashes2[j]) { i++; } else if (hashes2[j] < hashes1[i]) { j++; } else { intersectionCount++; i++; j++; } } numMatched += intersectionCount; numTried += indexEntry1.length + indexEntry2.length - intersectionCount; // Determine the next indices to check for collisions int nextIndex1Increment = 0; if (hashes1.Count > i_e) { do { nextIndex1Increment++; } while (!store1.index.ContainsKey(index1 + nextIndex1Increment)); } int nextIndex2Increment = 0; if (hashes2.Count > j_e) { do { nextIndex2Increment++; } while (!store2.index.ContainsKey(index2 + nextIndex2Increment)); } int nextIndexIncrement = Math.Min(nextIndex1Increment, nextIndex2Increment); index1 += nextIndexIncrement; index2 += nextIndexIncrement; frameCount += nextIndexIncrement; // Match detection // This approach trades the hash matching rate with time, i.e. the rate required // for a match drops with time, by using an exponentially with time decaying threshold. // The idea is that a high matching rate after a short time is equivalent to a low matching // rate after a long time. The difficulty is to to parameterize it in such a way, that a // match is detected as fast as possible, while detecting a no-match isn't delayed too far // as it takes a lot of processing time. // NOTE The current parameters are just eyeballed, there's a lot of influence on processing speed and matching rate here double rate = 1d / numTried * numMatched; if (frameCount >= matchingMaxFrames || rate < thresholdReject[frameCount]) { break; // exit condition } else if (frameCount > matchingMinFrames && rate > thresholdAccept[frameCount]) { matchFound = true; break; } if (nextIndexIncrement == 0) { // We reached the end of a hash list break; // Break the while loop } } if (matchFound) { matches.Add(new Match { Similarity = 1f / numTried * numMatched, Track1 = entry1.AudioTrack, Track1Time = SubFingerprintIndexToTimeSpan(entry1.Index), Track2 = entry2.AudioTrack, Track2Time = SubFingerprintIndexToTimeSpan(entry2.Index), Source = matchSourceName }); } } } } return(matches); }