Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
* develop:
  Version bump.
  Lets return coverages as we need it anyways.
  Adding a method that will split track matched regions, useful for streaming scenarios only.
  Update README.md
  • Loading branch information
AddictedCS committed Jul 22, 2020
2 parents 46a29a5 + 4509ffa commit 389b710
Show file tree
Hide file tree
Showing 8 changed files with 87 additions and 23 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,6 @@ Every `ResultEntry` object will contain the following information:
- `QueryMatchStartsAt` - returns time position where resulting track started to match in the query
- `TrackMatchStartsAt` - returns time position where the query started to match in the resulting track
- `TrackStartsAt` - returns an approximation where does the matched track starts, always relative to the query
- `Coverage` - returns a value between [0, 1], informing how much the query covered the resulting track (i.e. a 2 minutes query found a 30 seconds track within it, starting at 100th second, coverage will be equal to (120 - 100)/30 ~= 0.66)
- `Confidence` - returns a value between [0, 1]. A value below 0.15 is most probably a false positive. A value bigger than 0.15 is very likely to be an exact match. For good audio quality queries you can expect getting a confidence > 0.5.
- `MatchedAt` - returns timestamp showing at what time did the match occured. Usefull for realtime queries.

Expand All @@ -107,6 +106,8 @@ Every `ResultEntry` object will contain the following information:
- `TotalTracksAnalyzed` - total # of tracks analyzed during query time. If this number exceeds 50, try optimizing your configuration.
- `TotalFingerprintsAnalyzed` - total # of fingerprints analyzed during query time. If this number exceeds 500, try optimizing your configuration.

Read [Different Types of Coverage](https://github.com/AddictedCS/soundfingerprinting/wiki/Different-Types-of-Coverage) to understand how query coverage is calculated.

### Version 6.2.0
Version 6.2.0 provides ability to query realtime datasources. Usefull for scenarious when you would like to monitor a realtime stream and get matching results as fast as possible.

Expand Down
4 changes: 2 additions & 2 deletions src/SoundFingerprinting.Tests/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@
[assembly: AssemblyCulture("")]
[assembly: ComVisible(false)]
[assembly: Guid("4cac962e-ebc5-4006-a1e0-7ffb3e2483c2")]
[assembly: AssemblyVersion("7.4.6.100")]
[assembly: AssemblyInformationalVersion("7.4.6.100")]
[assembly: AssemblyVersion("7.4.7.100")]
[assembly: AssemblyInformationalVersion("7.4.7.100")]
40 changes: 30 additions & 10 deletions src/SoundFingerprinting.Tests/Unit/Query/MatchesTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ namespace SoundFingerprinting.Tests.Unit.Query
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using SoundFingerprinting.LCS;
using SoundFingerprinting.Query;

[TestFixture]
Expand All @@ -15,22 +16,22 @@ public void ShouldCollapseByQueryAt1()
// b ------- 8|18 + 10 = 18|28
var a = GetMatches(0f, 10f, 10d, 512f / 5512);
var b = GetMatches(8f, 18f, 10d, 512f / 5512);

Assert.IsTrue(a.TryCollapseWith(b, 1.48d, out var c));
Assert.AreEqual(19.5d, c.TotalLength, 0.1);
Assert.AreEqual(a.Count() + b.Count(), c.Count());
Assert.AreEqual(0d, c.QueryAtStartsAt);
Assert.AreEqual(10f, c.TrackAtStartsAt);
}

[Test]
public void ShouldCollapseByQueryAt2()
{
// a ------------- 0|12 + 12 = 12|24
// b ---- 4|16 + 4 = 8 |20
var a = GetMatches(0f, 12f, 12d, 512f / 5512);
var b = GetMatches(4f, 16f, 4d, 512f / 5512);

Assert.IsTrue(a.TryCollapseWith(b, 1.48d, out var c));
Assert.AreEqual(a.Count() + b.Count(), c.Count());
Assert.AreEqual(13.5d, c.TotalLength, 0.1);
Expand All @@ -45,7 +46,7 @@ public void ShouldIdentifyAsContainedWithinItself()

var a = GetMatches(0, 0, 120, 512f / 5512);
var b = GetMatches(10, 20, 10, 512f / 5512);

Assert.IsFalse(a.TryCollapseWith(b, 1.48d, out _));
Assert.IsTrue(a.Contains(b));
Assert.IsFalse(b.Contains(a));
Expand All @@ -58,10 +59,10 @@ public void ShouldNotCollapseByQueryAtAsGapIsTooBig()
// b -------- 12|22 + 10 = 22|32
var a = GetMatches(0f, 10f, 10d, 512f / 5512);
var b = GetMatches(12f, 22f, 10d, 512f / 5512);

Assert.IsFalse(a.TryCollapseWith(b, 1.48d, out _));
}

[Test]
public void ShouldNotCollapseAsQueryMatchCorrespondsTo2DifferentTracksLocation2()
{
Expand Down Expand Up @@ -94,7 +95,7 @@ public void ShouldNotCollapseAsQueryMatchesSameTrackMultipleTimes()
// query ----------------------
// track --------
// track --------

var a = GetMatches(0f, 0f, 15d, 512f / 5512);
var b = GetMatches(60f, 0f, 15d, 512f / 5512);

Expand All @@ -112,20 +113,39 @@ public void ShouldNotMergeWithTrackAtReversed()
Assert.IsFalse(b.TryCollapseWith(a, 1.48f, out _));
}

private static Matches GetMatches(float startQueryAt, float startTrackAt, double length, float stride)
[Test]
public void ShouldSplitMatchedRegionsToGapRegions()
{
// a ------ ------- -------
// t 0 10 20 30 32 40
float stride = 1;
var matches = GetMatches(0f, 0f, 10d, stride)
.Concat(GetMatches(20f, 20f, 10, stride, 20))
.Concat(GetMatches(32f, 32f, 8, stride, 32));

float fingerprintLengthInSeconds = 1f;
double permittedGap = 2.5;
var coverages = matches.SplitTrackMatchedRegions(permittedGap, fingerprintLengthInSeconds).ToList();

Assert.AreEqual(2, coverages.Count);
Assert.AreEqual(10 + fingerprintLengthInSeconds, coverages.First().CoverageWithPermittedGapsLength);
Assert.AreEqual(20 + fingerprintLengthInSeconds, coverages.Last().CoverageWithPermittedGapsLength);
}

private static Matches GetMatches(float startQueryAt, float startTrackAt, double length, float stride, int startIndex = 0)
{
const int hammingSimilarity = 100;
float startAt = 0f;
var matches = new List<MatchedWith>();
uint index = 0;
uint index = (uint)startIndex;
while (startAt <= length)
{
var match = new MatchedWith(index, startQueryAt + startAt, index, startTrackAt + startAt, hammingSimilarity);
matches.Add(match);
startAt += stride;
index++;
}

return new Matches(matches);
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/SoundFingerprinting/LCS/Coverage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ private Coverage()
public double TrackMatchStartsAt => BestPath.First().TrackMatchAt;

/// <summary>
/// Gets exact query coverage sum in seconds. Exact length of matched fingerprints, not necessary consecutive, just how much length has been covered by the query
/// Gets the total track length that was covered by the query. Exact length of matched fingerprints, not necessary consecutive.
/// </summary>
public double CoverageLength => DiscreteCoverageLength - BestPath.FindTrackGaps(TrackLength, 0, FingerprintLength).Where(d => !d.IsOnEdge).Sum(d => d.LengthInSeconds);

Expand Down
4 changes: 2 additions & 2 deletions src/SoundFingerprinting/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,5 @@
[assembly: InternalsVisibleTo("SoundFingerprinting.FFT.FFTW")]
[assembly: InternalsVisibleTo("SoundFingerprinting.FFT.FFTW.Tests")]

[assembly: AssemblyVersion("7.4.6.100")]
[assembly: AssemblyInformationalVersion("7.4.6.100")]
[assembly: AssemblyVersion("7.4.7.100")]
[assembly: AssemblyInformationalVersion("7.4.7.100")]
38 changes: 37 additions & 1 deletion src/SoundFingerprinting/Query/Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
using System;
using System.Linq;
using System.Collections.Generic;

using SoundFingerprinting.LCS;

public static class Extensions
{
private const double PermittedGapZero = 1e-5;
Expand All @@ -23,6 +24,41 @@ public static double StdDev(this IEnumerable<double> values)
return ret;
}

public static IEnumerable<Coverage> SplitTrackMatchedRegions(this IEnumerable<MatchedWith> entries, double permittedGap, double fingerprintLength)
{
var list = new List<Coverage>();
var ordered = entries.OrderBy(_ => _.TrackMatchAt).ToList();
if (!ordered.Any())
{
return list;
}

var stack = new Stack<MatchedWith>();
stack.Push(ordered.First());
foreach (var matchedWith in ordered.Skip(1))
{
var prev = stack.Peek();
if (SubFingerprintsToSeconds.GapLengthToSeconds(matchedWith.TrackMatchAt, prev.TrackMatchAt, fingerprintLength) > permittedGap)
{
list.Add(GetMatchedWithsFromStack(stack, permittedGap, fingerprintLength));
stack = new Stack<MatchedWith>();
}

stack.Push(matchedWith);
}

list.Add(GetMatchedWithsFromStack(stack, permittedGap, fingerprintLength));
return list;
}

private static Coverage GetMatchedWithsFromStack(Stack<MatchedWith> stack, double permittedGap, double fingerprintLengthInSeconds)
{
var matchedWiths = ((IEnumerable<MatchedWith>) stack.ToList()).Reverse().ToList();
var queryLength = SubFingerprintsToSeconds.MatchLengthToSeconds(matchedWiths.Last().QueryMatchAt, matchedWiths.First().QueryMatchAt, fingerprintLengthInSeconds);
var trackLength = SubFingerprintsToSeconds.MatchLengthToSeconds(matchedWiths.Last().TrackMatchAt, matchedWiths.First().TrackMatchAt, fingerprintLengthInSeconds);
return matchedWiths.EstimateCoverage(queryLength, trackLength, fingerprintLengthInSeconds, permittedGap);
}

public static IEnumerable<Gap> FindQueryGaps(this IEnumerable<MatchedWith> entries, double permittedGap, double fingerprintLength)
{
double sanitizedPermittedGap = permittedGap > 0 ? permittedGap : PermittedGapZero;
Expand Down
17 changes: 12 additions & 5 deletions src/SoundFingerprinting/Query/ResultEntry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,23 @@ public ResultEntry(TrackData track,
[ProtoMember(2)]
public Coverage Coverage { get; }

/// <summary>
/// Gets the total track length that was covered by the query. Exact length of matched fingerprints, not necessary consecutive.
/// </summary>
public double CoverageLength => Coverage.CoverageLength;

/// <summary>
/// Gets query coverage length with permitted gaps
/// </summary>
[ProtoMember(3)]
public double CoverageWithPermittedGapsLength { get; }

/// <summary>
/// Gets estimated track coverage inferred from matching start and end of the resulting track in the query
/// </summary>
[ProtoMember(11)]
public double DiscreteCoverageLength { get; }

/// <summary>
/// Gets the exact position in seconds where resulting track started to match in the query
/// </summary>
Expand Down Expand Up @@ -136,11 +147,7 @@ public ResultEntry(TrackData track,
[ProtoMember(10)]
public double Score { get; }

/// <summary>
/// Gets estimated track coverage inferred from matching start and end of the resulting track in the query
/// </summary>
[ProtoMember(11)]
public double DiscreteCoverageLength { get; }


/// <summary>
/// Gets information about gaps in the result entry coverage
Expand Down
2 changes: 1 addition & 1 deletion src/SoundFingerprinting/SoundFingerprinting.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<Nullable>enable</Nullable>
<PackageLicenseUrl>https://opensource.org/licenses/MIT</PackageLicenseUrl>
<PackageVersion>7.4.6</PackageVersion>
<PackageVersion>7.4.7</PackageVersion>
<Authors>Sergiu Ciumac</Authors>
<PackageDescription>SoundFingerprinting is a C# framework that implements an efficient algorithm of audio fingerprinting and identification. Designed for developers, enthusiasts, researchers in the fields of audio processing, data mining, digital signal processing.</PackageDescription>
<PackageProjectUrl>https://github.com/addictedcs/soundfingerprinting</PackageProjectUrl>
Expand Down

0 comments on commit 389b710

Please sign in to comment.