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);
        }
 public static AutoMixingResult Failure(AutoMixingContext context)
 {
     return new AutoMixingResult(
         mixedTracks: context.TracksToMix, 
         context: context, 
         unknownTracks: Enumerable.Empty<IMixItem>(),
         isSuccess: false);
 }
 public static AutoMixingResult Success(
     AutoMixingContext context, 
     IEnumerable<IMixItem> mixedTracks,
     IEnumerable<IMixItem> unknownTracks)
 {
     return new AutoMixingResult(
         mixedTracks: mixedTracks,
         context: context,
         unknownTracks: unknownTracks,
         isSuccess: true);
 }
 public AutoMixingResult(
     IEnumerable<IMixItem> mixedTracks,
     AutoMixingContext context,
     IEnumerable<IMixItem> unknownTracks,
     bool isSuccess)
 {
     if (mixedTracks == null) throw new ArgumentNullException("mixedTracks");
     if (unknownTracks == null) throw new ArgumentNullException("unknownTracks");
     if (context == null) throw new ArgumentNullException("context");
     MixedTracks = mixedTracks.ToList();
     UnknownTracks = unknownTracks.ToList();
     IsSuccess = isSuccess;
     Context = context;
 }
        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;
        }