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;
        }