public Visualizer( string songPath, string saveFile, Song song, Chart originalChart, ExpressedChart expressedChart, string expressedChartConfigName, PerformedChart performedChart, string performedChartConfigName, Chart generatedChart) { SongPath = songPath; SaveFile = saveFile; Song = song; OriginalChart = originalChart; ExpressedChart = expressedChart; ExpressedChartConfigName = expressedChartConfigName; PerformedChart = performedChart; PerformedChartConfigName = performedChartConfigName; GeneratedChart = generatedChart; ExpressedChartX = 0; foreach (var chartColInfo in ChartColumnInfo) { ExpressedChartX += chartColInfo.Width; } ExpressedChartX += (originalChart.NumInputs * ArrowW); GeneratedChartX = ExpressedChartX; foreach (var expressedColInfo in ExpressionColumnInfo) { GeneratedChartX += expressedColInfo.Width; } if (!SongPath.EndsWith(System.IO.Path.DirectorySeparatorChar.ToString())) { SongPath += System.IO.Path.DirectorySeparatorChar.ToString(); } // Determine the relative src path. if (string.IsNullOrEmpty(VisualizationDir)) { throw new Exception("VisualizationDir is not set. Set with InitializeVisualizationDir."); } SrcPath = Fumen.Path.GetRelativePath(SaveFile, GetSrcDir()); SrcPath = SrcPath.Replace('\\', '/'); if (!SrcPath.EndsWith("/")) { SrcPath += "/"; } }
/// <summary> /// Adds charts to the given song and write a visualization per chart, if configured to do so. /// </summary> /// <param name="song">Song to add charts to.</param> /// <param name="songArgs">SongArgs for the song file.</param> private static void AddCharts(Song song, SongArgs songArgs) { LogInfo("Processing Song.", songArgs.FileInfo, songArgs.RelativePath, song); var fileNameNoExtension = songArgs.FileInfo.Name; if (!string.IsNullOrEmpty(songArgs.FileInfo.Extension)) { fileNameNoExtension = fileNameNoExtension.Substring(0, songArgs.FileInfo.Name.Length - songArgs.FileInfo.Extension.Length); } var extension = songArgs.FileInfo.Extension.ToLower(); if (extension.StartsWith(".")) { extension = extension.Substring(1); } var newCharts = new List <Chart>(); var chartsIndicesToRemove = new List <int>(); foreach (var chart in song.Charts) { if (chart.Layers.Count == 1 && chart.Type == Config.Instance.InputChartType && chart.NumPlayers == 1 && chart.NumInputs == InputStepGraph.NumArrows && Config.Instance.DifficultyMatches(chart.DifficultyType)) { // Check if there is an existing chart. var(currentChart, currentChartIndex) = FindChart( song, Config.Instance.OutputChartType, chart.DifficultyType, OutputStepGraph.NumArrows); if (currentChart != null) { var fumenGenerated = GetFumenGeneratedVersion(currentChart, out var version); // Check if we should skip or overwrite the chart. switch (Config.Instance.OverwriteBehavior) { case OverwriteBehavior.DoNotOverwrite: continue; case OverwriteBehavior.IfFumenGenerated: if (!fumenGenerated) { continue; } break; case OverwriteBehavior.IfFumenGeneratedAndNewerVersion: if (!fumenGenerated || version >= Version) { continue; } break; case OverwriteBehavior.Always: default: break; } } // Create an ExpressedChart. var(ecc, eccName) = Config.Instance.GetExpressedChartConfig(songArgs.FileInfo, chart.DifficultyType); var expressedChart = ExpressedChart.CreateFromSMEvents( chart.Layers[0].Events, InputStepGraph, ecc, chart.DifficultyRating, GetLogIdentifier(songArgs.FileInfo, songArgs.RelativePath, song, chart)); if (expressedChart == null) { LogError("Failed to create ExpressedChart.", songArgs.FileInfo, songArgs.RelativePath, song, chart); continue; } // Create a PerformedChart. var(pcc, pccName) = Config.Instance.GetPerformedChartConfig(songArgs.FileInfo, chart.DifficultyType); var performedChart = PerformedChart.CreateFromExpressedChart( OutputStepGraph, pcc, OutputStartNodes, expressedChart, GeneratePerformedChartRandomSeed(songArgs.FileInfo.Name), GetLogIdentifier(songArgs.FileInfo, songArgs.RelativePath, song, chart)); if (performedChart == null) { LogError("Failed to create PerformedChart.", songArgs.FileInfo, songArgs.RelativePath, song, chart); continue; } // At this point we have succeeded, so add the chart index to remove if appropriate. if (currentChart != null) { chartsIndicesToRemove.Add(currentChartIndex); } // Create Events for the new Chart. var events = performedChart.CreateSMChartEvents(); CopyNonPerformanceEvents(chart.Layers[0].Events, events); events.Sort(new SMEventComparer()); // Sanity check on note counts. if (events.Count != chart.Layers[0].Events.Count) { var mineString = NoteChars[(int)NoteType.Mine].ToString(); // Disregard discrepancies in mine counts var newChartNonMineEventCount = 0; foreach (var newEvent in events) { if (newEvent.SourceType != mineString) { newChartNonMineEventCount++; } } var oldChartNonMineEventCount = 0; foreach (var oldEvent in chart.Layers[0].Events) { if (oldEvent.SourceType != mineString) { oldChartNonMineEventCount++; } } if (newChartNonMineEventCount != oldChartNonMineEventCount) { MetricPosition firstDiscrepancyPosition = null; var i = 0; while (i < events.Count && i < chart.Layers[0].Events.Count) { if (events[i].SourceType != chart.Layers[0].Events[i].SourceType || events[i].Position != chart.Layers[0].Events[i].Position) { firstDiscrepancyPosition = chart.Layers[0].Events[i].Position; break; } i++; } LogError( "Programmer error. Discrepancy in non-mine Event counts." + $" Old: {oldChartNonMineEventCount}, New: {newChartNonMineEventCount}." + $" First discrepancy position: {firstDiscrepancyPosition}.", songArgs.FileInfo, songArgs.RelativePath, song, chart); continue; } } // Create a new Chart for these Events. var newChart = new Chart { Artist = chart.Artist, ArtistTransliteration = chart.ArtistTransliteration, Genre = chart.Genre, GenreTransliteration = chart.GenreTransliteration, Author = FormatWithVersion(chart.Author), Description = FormatWithVersion(chart.Description), MusicFile = chart.MusicFile, ChartOffsetFromMusic = chart.ChartOffsetFromMusic, Tempo = chart.Tempo, DifficultyRating = chart.DifficultyRating, DifficultyType = chart.DifficultyType, Extras = chart.Extras, Type = Config.Instance.OutputChartType, NumPlayers = 1, NumInputs = OutputStepGraph.NumArrows }; newChart.Layers.Add(new Layer { Events = events }); newCharts.Add(newChart); LogInfo( $"Generated new {newChart.Type} {newChart.DifficultyType} Chart from {chart.Type} {chart.DifficultyType} Chart" + $" using ExpressedChartConfig \"{eccName}\" (BracketParsingMethod {expressedChart.GetBracketParsingMethod():G})" + $" and PerformedChartConfig \"{pccName}\".", songArgs.FileInfo, songArgs.RelativePath, song, newChart); // Write a visualization. if (Config.Instance.OutputVisualizations && CanOutputVisualizations) { var visualizationDirectory = Fumen.Path.Combine(VisualizationDir, songArgs.RelativePath); Directory.CreateDirectory(visualizationDirectory); var saveFile = Fumen.Path.GetWin32FileSystemFullPath( Fumen.Path.Combine(visualizationDirectory, $"{fileNameNoExtension}-{chart.DifficultyType}-{extension}.html")); try { var visualizer = new Visualizer( songArgs.CurrentDir, saveFile, song, chart, expressedChart, eccName, performedChart, pccName, newChart ); visualizer.Write(); } catch (Exception e) { LogError($"Failed to write visualization to \"{saveFile}\". {e}", songArgs.FileInfo, songArgs.RelativePath, song, newChart); } } } } LogInfo( $"Generated {newCharts.Count} new {Config.Instance.OutputChartType} Charts (replaced {chartsIndicesToRemove.Count}).", songArgs.FileInfo, songArgs.RelativePath, song); // Remove overwritten charts. if (chartsIndicesToRemove.Count > 0) { // Ensure the indices are sorted descending so they don't shift when removing. chartsIndicesToRemove.Sort((a, b) => b.CompareTo(a)); foreach (var i in chartsIndicesToRemove) { song.Charts.RemoveAt(i); } } // Add new charts. song.Charts.AddRange(newCharts); }