using MeetingAssistant; using MeetingAssistant.Speakers; using Microsoft.Extensions.Logging.Abstractions; namespace MeetingAssistant.Tests; public sealed class ResemblyzerVoiceClusterMatcherTests { [Fact] public void CoherentSimilarClusterSelectsUnambiguousIdentity() { var matcher = CreateMatcher(); var query = Enumerable.Range(0, 5) .Select(index => UnitVector(0, secondaryDimension: index + 2, secondaryValue: 0.05f)) .ToList(); var candidates = new[] { new ResemblyzerVoiceVectorCandidate(42, [UnitVector(0), UnitVector(0, 10, 0.02f)]), new ResemblyzerVoiceVectorCandidate(77, [UnitVector(1), UnitVector(1, 11, 0.02f)]) }; var result = matcher.Match(query, candidates); Assert.True(result.Accepted); Assert.Equal(42, result.IdentityId); Assert.True(result.Cohesion >= 0.99); Assert.True(result.BestSimilarity >= 0.99); Assert.True(result.RunnerUpSimilarity < 0.1); } [Fact] public void FourVectorsDoNotTriggerAutomaticMatching() { var result = CreateMatcher().Match( Enumerable.Repeat(UnitVector(0), 4).ToList(), [new ResemblyzerVoiceVectorCandidate(42, [UnitVector(0)])]); Assert.False(result.Accepted); Assert.Contains("4/5", result.Reason); } [Fact] public void IncoherentClusterIsRejectedBeforeIdentityScoring() { var result = CreateMatcher().Match( Enumerable.Range(0, 5).Select(index => UnitVector(index)).ToList(), [new ResemblyzerVoiceVectorCandidate(42, [UnitVector(0)])]); Assert.False(result.Accepted); Assert.Contains("cohesion", result.Reason); Assert.Null(result.BestSimilarity); } [Fact] public void SimilarityBelowThresholdIsRejected() { var result = CreateMatcher().Match( Enumerable.Repeat(UnitVector(0), 5).ToList(), [new ResemblyzerVoiceVectorCandidate(42, [UnitVector(1)])]); Assert.False(result.Accepted); Assert.Contains("best similarity", result.Reason); Assert.Equal(0, result.BestSimilarity); } [Fact] public void SimilarCandidatesWithinMarginAreRejected() { var result = CreateMatcher().Match( Enumerable.Repeat(UnitVector(0), 5).ToList(), [ new ResemblyzerVoiceVectorCandidate(42, [UnitVector(0)]), new ResemblyzerVoiceVectorCandidate(77, [UnitVector(0, 1, 0.01f)]) ]); Assert.False(result.Accepted); Assert.Contains("margin", result.Reason); Assert.NotNull(result.RunnerUpSimilarity); } [Fact] public void InvalidStoredCandidateDoesNotAbortScoringOtherIdentities() { var result = CreateMatcher().Match( Enumerable.Repeat(UnitVector(0), 5).ToList(), [ new ResemblyzerVoiceVectorCandidate(13, [new float[256]]), new ResemblyzerVoiceVectorCandidate(42, [UnitVector(0)]) ]); Assert.True(result.Accepted); Assert.Equal(42, result.IdentityId); } private static ResemblyzerVoiceClusterMatcher CreateMatcher() { return new ResemblyzerVoiceClusterMatcher( new ResemblyzerSpeakerRecognitionOptions { RequiredVectorsPerSpeaker = 5, MinimumClusterCohesion = 0.75, MinimumIdentitySimilarity = 0.75, MinimumSimilarityMargin = 0.05 }, NullLogger.Instance); } private static float[] UnitVector( int primaryDimension, int? secondaryDimension = null, float secondaryValue = 0) { var vector = new float[256]; vector[primaryDimension] = 1; if (secondaryDimension is { } dimension) { vector[dimension] = secondaryValue; } return vector; } }