예제 #1
0
        /// <summary>
        /// Process one song.
        /// Song in this context is a song file.
        /// Some songs have multiple song files (an sm and an ssc version).
        /// Will add charts, copy the charts and non-chart files to the output directory,
        /// and write visualizations for the conversion.
        /// </summary>
        /// <param name="songArgs">SongArgs for the song file.</param>
        private static async Task ProcessSong(SongArgs songArgs)
        {
            // Load the song.
            Song song;

            try
            {
                var reader = Reader.CreateReader(songArgs.FileInfo);
                if (reader == null)
                {
                    LogError("Unsupported file format. Cannot parse.", songArgs.FileInfo, songArgs.RelativePath);
                    return;
                }

                song = await reader.Load();
            }
            catch (Exception e)
            {
                LogError($"Failed to load file. {e}", songArgs.FileInfo, songArgs.RelativePath);
                return;
            }

            // Add new charts.
            AddCharts(song, songArgs);

            // Save
            var saveFile = Fumen.Path.GetWin32FileSystemFullPath(Fumen.Path.Combine(songArgs.SaveDir, songArgs.FileInfo.Name));
            var config   = new SMWriterBase.SMWriterBaseConfig
            {
                FilePath = saveFile,
                Song     = song,
                MeasureSpacingBehavior   = SMWriterBase.MeasureSpacingBehavior.UseSubDivisionDenominatorAsMeasureSpacing,
                PropertyEmissionBehavior = SMWriterBase.PropertyEmissionBehavior.MatchSource
            };
            var fileFormat = FileFormat.GetFileFormatByExtension(songArgs.FileInfo.Extension);

            switch (fileFormat.Type)
            {
            case FileFormatType.SM:
                new SMWriter(config).Save();
                break;

            case FileFormatType.SSC:
                new SSCWriter(config).Save();
                break;

            default:
                LogError("Unsupported file format. Cannot save.", songArgs.FileInfo, songArgs.RelativePath);
                break;
            }

            // Copy the non-chart files.
            CopyNonChartFiles(songArgs.CurrentDir, songArgs.SaveDir);
        }
예제 #2
0
        /// <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);
        }