public AutoMixingResult AutoMix(AutoMixingContext context) { if (context == null) throw new ArgumentNullException("context"); IEnumerable<IMixItem> unknownTracks = context.TracksToMix.Where(c => c.IsUnknownKeyOrBpm).ToList(); IEnumerable<IMixItem> mixableTracks = context.TracksToMix.Except(unknownTracks); AutoMixingBucket optionalStartVertex; AdjacencyGraph<AutoMixingBucket, AutoMixEdge> graph = BuildGraph(mixableTracks, unknownTracks, context, out optionalStartVertex); AddPreferredEdges(graph); IEnumerable<AutoMixingBucket> mixedBuckets = GetPreferredMix(graph, optionalStartVertex, context.GetOptionalEndKey(), computeAll: false); if (mixedBuckets == null) { AddFallbackEdges(graph); mixedBuckets = GetPreferredMix(graph, optionalStartVertex, context.GetOptionalEndKey(), computeAll: true); } if (mixedBuckets == null) { Log.Debug("Failed to find any auto mix paths. Returning unmodified tracks."); return AutoMixingResult.Failure(context); } IEnumerable<IMixItem> mixedTracks = UnpackBuckets(mixedBuckets); return AutoMixingResult.Success(context, mixedTracks, unknownTracks); }
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; }