static AdjacencyGraph<AutoMixingBucket, AutoMixEdge> BuildGraph( IEnumerable<IMixItem> mixableTracks, IEnumerable<IMixItem> unknownTracks, AutoMixingContext context, out AutoMixingBucket optionalStartVertex) { IList<AutoMixingBucket> buckets = mixableTracks .GroupBy(g => g.ActualKey) .Select(g => new AutoMixingBucket(g, g.Key)) .ToList(); Log.DebugFormat("{0} mixable tracks ({1} buckets aka vertices), {2} unmixable tracks (unknown key/BPM).", mixableTracks.Count(), buckets.Count(), unknownTracks.Count()); var graph = new AdjacencyGraph<AutoMixingBucket, AutoMixEdge>(); graph.AddVertexRange(buckets); // If we are auto-mixing a subset of tracks within a mix, we must // keep them harmonically compatible with the tracks immediately // before and after i.e. (preceeding)(tracks to mix)(following). // // To find such a path, we unfortunately can't just look for paths // where (start = preceeding and end = following) because they // might be using different keys that are not used by any of the // tracks in the graph. // // To get around this problem, we add empty 'placeholder' buckets // (vertices) to the graph for the preceeding/following keys, and // look for paths that use these vertices as start/end points. // This works because when we unpack the buckets, these vertices // simply unpack as empty (they don't contain any tracks) so we can // use them for the final path of mix items without producing any // messy placeholder/temporary tracks. optionalStartVertex = AddPlaceholderVertexIfRequired(graph, context.GetOptionalStartKey()); AddPlaceholderVertexIfRequired(graph, context.GetOptionalEndKey()); return graph; }
static IEnumerable<AutoMixingBucket> GetPreferredMix( IVertexListGraph<AutoMixingBucket, AutoMixEdge> graph, AutoMixingBucket optionalStartVertex, HarmonicKey? optionalEndKey, bool computeAll) { LongestPathAlgorithm<AutoMixingBucket, AutoMixEdge> algo; if (!computeAll && (optionalEndKey == null && optionalStartVertex == null)) algo = new LongestPathAlgorithm<AutoMixingBucket, AutoMixEdge>(graph); else algo = new AllLongestPathsAlgorithm<AutoMixingBucket, AutoMixEdge>(graph); HarmonicKey? optionalStartKey = null; if (optionalStartVertex != null) { algo.SetRootVertex(optionalStartVertex); optionalStartKey = optionalStartVertex.ActualKey; } var stopwatch = Stopwatch.StartNew(); algo.Compute(); stopwatch.Stop(); Log.DebugFormat("Found {0} paths in {1}.", algo.LongestPaths.Count(), stopwatch.Elapsed); if (!algo.LongestPaths.Any()) return null; LogPaths(algo.LongestPaths); IEnumerable<AutoMixEdge> bestPath = GetPathSatisfyingOptionalStartAndEndKey(algo.LongestPaths, optionalStartKey, optionalEndKey); if (bestPath == null) return null; Log.DebugFormat("Using path: {0}", FormatPath(bestPath)); return GetVertices(bestPath); }
static AutoMixingBucket AddPlaceholderVertexIfRequired( AdjacencyGraph<AutoMixingBucket, AutoMixEdge> graph, HarmonicKey? key) { if (!key.HasValue) return null; var vertex = new AutoMixingBucket(key.Value); graph.AddVertex(vertex); return vertex; }