private List<Track> GetWorkingTracks(Track currentTrack, IReadOnlyList<Track> currentPath, Track workingTrack, ICollection<string> availableTrackDescriptions, ICollection<string> excludeTrackDescriptions, KeyMixStrategy keyMixStrategy) { var mixTracks = new List<Track>(); if (currentTrack.Title == workingTrack.Title) { mixTracks = GetUnrankedTracks(workingTrack, currentPath, availableTrackDescriptions, excludeTrackDescriptions, keyMixStrategy, false).Take(1).ToList(); } else { mixTracks.Add(workingTrack); } return mixTracks; }
public int GetAdjustedKeyMixRank(Track track1, Track track2, KeyMixStrategy keyMixStrategy) { var keyRank = KeyHelper.GetKeyMixRank(track1.Key, track2.Key); if (keyMixStrategy == KeyMixStrategy.Good || keyMixStrategy == KeyMixStrategy.VeryGood) return keyRank; var mixLevel = MixLibrary.GetExtendedMixLevel(track1, track2); if (mixLevel >= 5) keyRank += 3; else if (mixLevel >= 4) keyRank += 2; else if (mixLevel >= 3) keyRank += 1; if (keyRank > 5) keyRank = 5; return keyRank; }
private List<Track> GetUnrankedTracks(Track currentTrack, IReadOnlyList<Track> currentPath, ICollection<string> availableTrackDescriptions, ICollection<string> excludeTrackDescriptions, KeyMixStrategy keyMixStrategy, bool toTracksOnly) { var mixTracks = MixLibrary.GetUnrankedToTracks(currentTrack).ToList(); if (!toTracksOnly) mixTracks.AddRange(MixLibrary.GetUnrankedFromTracks(currentTrack)); mixTracks = mixTracks.Where(t => !excludeTrackDescriptions.Contains(t.Description)) .Where(t => availableTrackDescriptions.Contains(t.Description)) .Distinct() .ToList(); FilterByKeyMixStrategy(currentTrack, mixTracks, keyMixStrategy); if (mixTracks.Count == 0) { // find all tracks in the list that have already been mixed into from the current track var excludeTracks = new List<Track>(); for (var i = 1; i < currentPath.Count; i++) { if (currentPath[i - 1].Title == currentTrack.Title) excludeTracks.Add(currentPath[i]); } // get all unranked tracks apart from those ones mixTracks = MixLibrary.GetUnrankedToTracks(currentTrack) .Union(MixLibrary.GetUnrankedFromTracks(currentTrack)) .Except(excludeTracks) .Where(t => availableTrackDescriptions.Contains(t.Description)) .Distinct() .ToList(); } FilterByKeyMixStrategy(currentTrack, mixTracks, keyMixStrategy); mixTracks = mixTracks.OrderByDescending(t => KeyHelper.GetKeyMixRank(currentTrack.Key, t.Key)) .ThenBy(t => t.Rank) .ThenBy(t => BpmHelper.GetAdjustedBpmPercentChange(currentTrack.EndBpm, t.StartBpm)) .ToList(); return mixTracks; }
private void FilterByKeyMixStrategy(Track currentTrack, List<Track> mixTracks, KeyMixStrategy keyMixStrategy) { if (keyMixStrategy == KeyMixStrategy.Any) return; var minimumKeyMixRank = 0; if (keyMixStrategy == KeyMixStrategy.VeryGood) minimumKeyMixRank = 4; if (keyMixStrategy == KeyMixStrategy.Bearable) minimumKeyMixRank = 2; if (keyMixStrategy == KeyMixStrategy.Good || keyMixStrategy == KeyMixStrategy.GoodIfPossible) minimumKeyMixRank = 3; if (minimumKeyMixRank == 0) return; if (keyMixStrategy == KeyMixStrategy.GoodIfPossible) { var notGoodCount = mixTracks.Count(t => GetAdjustedKeyMixRank(currentTrack, t, keyMixStrategy) < minimumKeyMixRank); if (notGoodCount == mixTracks.Count) minimumKeyMixRank = 2; } mixTracks.RemoveAll(t => GetAdjustedKeyMixRank(currentTrack, t, keyMixStrategy) < minimumKeyMixRank); }
/// <summary> /// Filters the mixable tracks. /// </summary> /// <param name="currentTrack">The current track.</param> /// <param name="currentPath">The current path.</param> /// <param name="restrictArtistClumping">If set to true, restrict genre clumping.</param> /// <param name="restrictGenreClumping">If set to true, restrict genre clumping.</param> /// <param name="restrictTitleClumping">If set to true, restrict title clumping.</param> /// <param name="keyMixStrategy">The key mixing strategy.</param> /// <param name="mixTracks">The mix tracks.</param> private void FilterMixableTracks(Track currentTrack, IReadOnlyList<Track> currentPath, bool restrictArtistClumping, bool restrictGenreClumping, bool restrictTitleClumping, KeyMixStrategy keyMixStrategy, List<Track> mixTracks) { var cycleTracks = GetCycleHistory(currentPath); if (restrictTitleClumping) FilterMixableTracksByTitle(mixTracks, cycleTracks); if (restrictArtistClumping) FilterMixableTracksByArtist(mixTracks, cycleTracks); if (restrictGenreClumping) FilterMixableTracksByGenre(mixTracks, cycleTracks); FilterByKeyMixStrategy(currentTrack, mixTracks, keyMixStrategy); }
/// <summary> /// Gets the best mix tracks for play-list generation. /// </summary> /// <param name="currentTrack">The current track.</param> /// <param name="currentPath">The current path.</param> /// <param name="allowBearable">The allow bearable.</param> /// <param name="availableTrackDescriptions">The available track descriptions.</param> /// <param name="excludeTrackDescriptions">The exclude track descriptions.</param> /// <param name="restrictArtistClumping">If set to true, genre clumping is restricted.</param> /// <param name="restrictGenreClumping">if set to true> restrict genre clumping.</param> /// <param name="restrictTitleClumping">if set to true restrict title clumping.</param> /// <param name="keyMixStrategy">The key mix strategy.</param> /// <returns> /// A list of filtered mix tracks /// </returns> private List<Track> GetBestMixTracks(Track currentTrack, List<Track> currentPath, AllowBearableMixStrategy allowBearable, ICollection<string> availableTrackDescriptions, ICollection<string> excludeTrackDescriptions, bool restrictArtistClumping, bool restrictGenreClumping, bool restrictTitleClumping, KeyMixStrategy keyMixStrategy) { var mixTracks = MixLibrary .GetGoodTracks(currentTrack) .Where(t => !excludeTrackDescriptions.Contains(t.Description)) .Where(t => availableTrackDescriptions.Contains(t.Description)) .ToList(); FilterMixableTracks(currentTrack, currentPath, restrictArtistClumping, restrictGenreClumping, restrictTitleClumping, keyMixStrategy, mixTracks); var bearableAllowed = IsBearableTrackMixAllowed(currentTrack, currentPath, allowBearable, mixTracks); if (!bearableAllowed) mixTracks.RemoveAll(t => t.Rank <= 2); if (bearableAllowed) { var bearableTracks = MixLibrary .GetBearableTracks(currentTrack) .Where(t => !excludeTrackDescriptions.Contains(t.Description)) .Where(t => availableTrackDescriptions.Contains(t.Description)) .ToList(); mixTracks.AddRange(bearableTracks); } FilterMixableTracks(currentTrack, currentPath, restrictArtistClumping, restrictGenreClumping, restrictTitleClumping, keyMixStrategy, mixTracks); return mixTracks; }
private void GeneratePaths(Direction direction, AllowBearableMixStrategy allowBearable, MixStrategy strategy, UseExtendedMixes useExtendedMixes, IReadOnlyDictionary<string, Dictionary<string, Track>> excludedMixes, bool restrictArtistClumping, bool restrictGenreClumping, bool restrictTitleClumping, KeyMixStrategy keyMixStrategy, Track workingTrack, ICollection<string> availableTrackDescriptions, List<TrackPath> nextPaths, TrackPath currentPath) { var currentTrack = currentPath.Tracks.Last(); DebugHelper.WriteLine("Start GeneratePaths " + currentTrack.Description); var excludeTrackDescriptions = GetDistinctTrackDescriptions(currentPath.Tracks); List<Track> mixTracks; if (strategy == MixStrategy.BestMix || strategy == MixStrategy.Variety || strategy == MixStrategy.ExtraVariety) { mixTracks = GetBestMixTracks(currentTrack, currentPath.Tracks, allowBearable, availableTrackDescriptions, excludeTrackDescriptions, restrictArtistClumping, restrictGenreClumping, restrictTitleClumping, keyMixStrategy); } else if (strategy == MixStrategy.Unranked) { mixTracks = GetUnrankedTracks(currentTrack, currentPath.Tracks, availableTrackDescriptions, excludeTrackDescriptions, keyMixStrategy, true); } else if (strategy == MixStrategy.Working) { mixTracks = GetWorkingTracks(currentTrack, currentPath.Tracks, workingTrack, availableTrackDescriptions, excludeTrackDescriptions, keyMixStrategy); } else { mixTracks = new List<Track>(); } if (direction != Direction.Any) { var preferredDirection = direction; if (preferredDirection == Direction.Cycle) preferredDirection = GetPreferredDirection(currentTrack, currentPath.Tracks); var filteredTracks = FilterTracksByDirection(currentTrack, mixTracks, preferredDirection); if (filteredTracks.Count > 0) mixTracks = filteredTracks; } if ((strategy == MixStrategy.BestMix || strategy == MixStrategy.Variety || strategy == MixStrategy.ExtraVariety) && useExtendedMixes != UseExtendedMixes.Any) { mixTracks = FilterTracksByExtendedMix(currentTrack, mixTracks, useExtendedMixes); } if (excludedMixes != null) { mixTracks = FilterExcludedMixes(currentTrack, mixTracks, excludedMixes); } if (strategy == MixStrategy.BestMix || strategy == MixStrategy.Variety || strategy == MixStrategy.ExtraVariety) { mixTracks = mixTracks .OrderByDescending(x => GetAverageTrackAndMixAndKeyRank(currentTrack, x)) .ThenBy(x => x.Length) .ToList(); if (strategy == MixStrategy.Variety) { mixTracks = FilterTracksForVariety(currentTrack, mixTracks); } else if (strategy == MixStrategy.ExtraVariety) { mixTracks = FilterTracksForExtraVariety(currentTrack, mixTracks); } } var max = 3*Environment.ProcessorCount; mixTracks = mixTracks .Take(max) .ToList(); var trackPaths = mixTracks.Select(mixTrack => new TrackPath(mixTrack, currentPath)); foreach (var newPath in trackPaths) { lock (nextPaths) { nextPaths.Add(newPath); CalculateAverageRankForPath(newPath); } if (IsGenerationHalted()) break; } DebugHelper.WriteLine("End GeneratePaths " + currentTrack.Description); }
public List<Track> GeneratePlayList(List<Track> availableTracks, MixLibrary mixLibrary, List<Track> currentPlaylist, Direction direction, int approximateLength, AllowBearableMixStrategy allowBearable, MixStrategy strategy, UseExtendedMixes useExtendedMixes, Dictionary<string, Dictionary<string, Track>> excludedMixes, bool restrictArtistClumping, bool restrictGenreClumping, bool restrictTitleClumping, ContinueMix continueMix, KeyMixStrategy keyMixStrategy, int maxTracksToAdd) { if (strategy == MixStrategy.Working && currentPlaylist.Count == 0) return currentPlaylist; Track workingTrack = null; if (strategy == MixStrategy.Working) { direction = Direction.Any; workingTrack = currentPlaylist.Last(); } GeneratePlayListStatus = ""; MixLibrary = mixLibrary; AvailableTracks = availableTracks; if (strategy == MixStrategy.Working) AvailableTracks.RemoveAll(t => MixLibrary.GetMixOutCount(t) == 0); if (AvailableTracks.Count == 0) return currentPlaylist; var availableTrackDescriptions = GetDistinctTrackDescriptions(AvailableTracks); var trackCountLimit = int.MaxValue; if (approximateLength > 0 && approximateLength != int.MaxValue) { var currentLength = currentPlaylist.Sum(cp => cp.Length); var requiredLength = Convert.ToInt32((approximateLength*60) - currentLength); if (requiredLength <= 0) return new List<Track>(); var averageLength = AvailableTracks.Average(t => t.Length); trackCountLimit = (requiredLength/Convert.ToInt32(averageLength)) + currentPlaylist.Count; if (trackCountLimit == currentPlaylist.Count) return new List<Track>(); } if (maxTracksToAdd != int.MaxValue && trackCountLimit > currentPlaylist.Count + maxTracksToAdd) { trackCountLimit = currentPlaylist.Count + maxTracksToAdd; } if (strategy != MixStrategy.Unranked && strategy != MixStrategy.Working) { if (trackCountLimit > AvailableTracks.Count) { trackCountLimit = AvailableTracks.Count; } } var initialPlaylistCount = currentPlaylist.Count; var currentPaths = new List<TrackPath>(); if (currentPlaylist.Count == 0) { var trackPaths = AvailableTracks.Select(track => new TrackPath(track)); foreach (var path in trackPaths) { CalculateAverageRankForPath(path); currentPaths.Add(path); } } else if (continueMix == ContinueMix.No) { var trackPaths = AvailableTracks .Select(track => new List<Track>(currentPlaylist) {track}) .Select(playlist => new TrackPath(playlist)); foreach (var path in trackPaths) { CalculateAverageRankForPath(path); currentPaths.Add(path); } } else { var path = new TrackPath(currentPlaylist); CalculateAverageRankForPath(path); currentPaths.Add(path); } _cancelGeneratePlayList = false; _stopGeneratePlayList = false; var nextPaths = new List<TrackPath>(); while (!IsGenerationHalted()) { ParallelHelper.ForEach(currentPaths, currentPath => GeneratePaths(direction, allowBearable, strategy, useExtendedMixes, excludedMixes, restrictArtistClumping, restrictGenreClumping, restrictTitleClumping, keyMixStrategy, workingTrack, availableTrackDescriptions, nextPaths, currentPath)); if (IsGenerationHalted()) break; if (nextPaths.Count == 0) break; GeneratePlayListStatus = $"Generated {nextPaths.Count} possible paths for {nextPaths[0].Tracks.Count} of {trackCountLimit} tracks."; var max = 50*Environment.ProcessorCount; if (nextPaths.Count > max) { nextPaths = nextPaths .OrderByDescending(t => t.AverageRank) .Take(max) .ToList(); } currentPaths.Clear(); currentPaths.AddRange(nextPaths); if (nextPaths[0].Tracks.Count >= trackCountLimit) break; nextPaths.Clear(); } if (_cancelGeneratePlayList) return currentPlaylist; var resultPath = currentPaths .OrderByDescending(t => GetAverageTrackAndMixAndKeyRank(t.Tracks)) .FirstOrDefault(); if ((strategy == MixStrategy.BestMix || strategy == MixStrategy.Variety || strategy == MixStrategy.ExtraVariety) && resultPath != null && resultPath.Tracks.Count < trackCountLimit && resultPath.Tracks.Count > 0) { availableTrackDescriptions = GetDistinctTrackDescriptions(AvailableTracks); var excludeTrackDescriptions = GetDistinctTrackDescriptions(resultPath.Tracks); var currentTrack = resultPath.Tracks[resultPath.Tracks.Count - 1]; var nextTrack = GetBestMixTracks(currentTrack, resultPath.Tracks, allowBearable, availableTrackDescriptions, excludeTrackDescriptions, restrictArtistClumping, restrictArtistClumping, restrictTitleClumping, keyMixStrategy) .OrderBy(t => GetAverageTrackAndMixAndKeyRank(currentTrack, t)) .FirstOrDefault(); if (nextTrack != null) resultPath.Tracks.Add(nextTrack); } var resultTracks = (resultPath != null) ? resultPath.Tracks : new List<Track>(); if (continueMix == ContinueMix.IfPossible && resultTracks.Count == initialPlaylistCount) { return GeneratePlayList(availableTracks, mixLibrary, currentPlaylist, direction, approximateLength, allowBearable, strategy, useExtendedMixes, excludedMixes, restrictArtistClumping, restrictGenreClumping, restrictTitleClumping, ContinueMix.No, keyMixStrategy, maxTracksToAdd); } return resultTracks; }