Newer
Older
Qwilight / Qwilight / Compiler / BaseCompiler.cs
using MoonSharp.Interpreter;
using Qwilight.Compute;
using Qwilight.Note;
using Qwilight.NoteFile;
using Qwilight.Utilities;
using System.IO;

namespace Qwilight.Compiler
{
    public abstract class BaseCompiler
    {
        public static BaseCompiler GetCompiler(BaseNoteFile noteFile, CancellationTokenSource setCancelCompiler) => noteFile switch
        {
            PMSFile pmsFile => new PMSCompiler(pmsFile, setCancelCompiler),
            BMSONFile bmsonFile => new BMSONCompiler(bmsonFile, setCancelCompiler),
            BMSFile bmsFile => new BMSCompiler(bmsFile, setCancelCompiler),
            _ => default
        };

        public Component.InputMode InputMode { get; set; }

        public BaseNoteFile NoteFile { get; }

        public CancellationTokenSource SetCancelCompiler { get; }

        public int NoteFormatID { get; }

        public List<BaseNote> Notes { get; } = new();

        public Component ComponentValue { get; set; }

        public BaseCompiler(BaseNoteFile noteFile, CancellationTokenSource setCancelCompiler)
        {
            NoteFile = noteFile;
            SetCancelCompiler = setCancelCompiler;
            NoteFormatID = DB.Instance.GetFormat(noteFile);
        }

        public abstract void CompileImpl(Computing targetComputing, byte[] noteFileContents, int salt);

        public abstract void CompileImpl(DefaultCompute defaultComputer, byte[] noteFileContents, bool loadParallelItems);

        public Dictionary<double, double> WaitBPMMap { get; } = new();

        public Dictionary<double, double> WaitStopMap { get; } = new();

        public SortedDictionary<double, double> PositionBPMMap { get; } = new();

        public SortedDictionary<double, int> PositionStandNoteCountMap { get; } = new();

        public SortedDictionary<double, double> WaitMultiplierMap { get; } = new(Comparer<double>.Create((x, y) => y.CompareTo(x)));

        public double HighestPosition { get; set; }

        public abstract double GetWaitValue(double waitPosition);

        void OnCompiled(Computing targetComputing)
        {
            targetComputing.InputMode = InputMode;
        }

        public void Compile(Computing targetComputing, byte[] noteFileContents, int salt)
        {
            try
            {
                HandleCompile(targetComputing, noteFileContents, salt);
                OnCompiled(targetComputing);
                targetComputing.OnCompiled();
            }
            catch (Exception e)
            {
                targetComputing.OnFault(e);
            }
        }

        void HandleCompile(Computing targetComputing, byte[] noteFileContents, int salt)
        {
            CompileImpl(targetComputing, noteFileContents, salt);
            if (!string.IsNullOrEmpty(targetComputing.NoteDrawingName))
            {
                targetComputing.NoteDrawingPath = Path.Combine(NoteFile.EntryItem.EntryPath, targetComputing.NoteDrawingName);
            }
            if (!string.IsNullOrEmpty(targetComputing.BannerDrawingName))
            {
                targetComputing.BannerDrawingPath = Path.Combine(NoteFile.EntryItem.EntryPath, targetComputing.BannerDrawingName);
            }
            var trailerAudioName = targetComputing.TrailerAudioName;
            targetComputing.TrailerAudioPath = Path.Combine(NoteFile.EntryItem.EntryPath, string.IsNullOrEmpty(trailerAudioName) ? "PREVIEW.WAV" : trailerAudioName);
            var rawHighestBPM = targetComputing.LevyingBPM;
            if (PositionBPMMap.Count > 0)
            {
                targetComputing.LowestBPM = Math.Min(PositionBPMMap.Values.Min(), targetComputing.LevyingBPM);
                targetComputing.HighestBPM = Math.Max(PositionBPMMap.Values.Max(), targetComputing.LevyingBPM);
                rawHighestBPM = targetComputing.HighestBPM;
                var bpmWaitMap = new Dictionary<double, double>();
                var lastBPM = targetComputing.LevyingBPM;
                var lastBPMPosition = 0.0;
                foreach (var (bpmPosition, bpm) in PositionBPMMap)
                {
                    if (bpmPosition <= HighestPosition)
                    {
                        bpmWaitMap[lastBPM] = bpmWaitMap.GetValueOrDefault(lastBPM) + GetWaitValue(bpmPosition) - GetWaitValue(lastBPMPosition);
                        lastBPMPosition = bpmPosition;
                        lastBPM = bpm;
                    }
                }
                bpmWaitMap[lastBPM] = bpmWaitMap.GetValueOrDefault(lastBPM) + GetWaitValue(HighestPosition) - GetWaitValue(lastBPMPosition);
                var longestWait = bpmWaitMap.Values.Max();
                var longestBPMs = bpmWaitMap.Where(pair => pair.Value == longestWait).Select(pair => pair.Key).ToArray();
                if (longestBPMs.Length > 0)
                {
                    var validBPMs = longestBPMs.Where(bpm => bpm > 0.5 && bpm < 65536).ToArray();
                    if (validBPMs.Length > 0)
                    {
                        targetComputing.BPM = validBPMs.Min();
                    }
                    else
                    {
                        var validBPMWaitValues = bpmWaitMap.Where(pair =>
                        {
                            var bpm = pair.Key;
                            return bpm > 0.5 && bpm < 65536;
                        }).ToArray();
                        if (validBPMWaitValues.Length > 0)
                        {
                            var validLongestWait = validBPMWaitValues.Max(pair => pair.Value);
                            targetComputing.BPM = validBPMWaitValues.Where(pair => pair.Value == validLongestWait).Min().Key;
                        }
                        else
                        {
                            var bpm = longestBPMs.Max();
                            if (bpm % 100001 == 0)
                            {
                                targetComputing.BPM = bpm / 100001;
                            }
                            else
                            {
                                targetComputing.BPM = bpm;
                            }
                        }
                    }
                }
            }
            else
            {
                if (targetComputing.LevyingBPM % 100001 == 0)
                {
                    targetComputing.BPM = targetComputing.LevyingBPM / 100001;
                }
                else
                {
                    targetComputing.BPM = targetComputing.LevyingBPM;
                }
                targetComputing.LowestBPM = targetComputing.BPM;
                targetComputing.HighestBPM = targetComputing.BPM;
            }
            targetComputing.Length = GetWaitValue(HighestPosition);
            targetComputing.IsHellBPM &= rawHighestBPM >= 65536;
            if (PositionStandNoteCountMap.Count > 0)
            {
                var positionStandNoteCounts = PositionStandNoteCountMap.ToArray();
                var lowestPosition = 0;
                var lowestWait = GetWaitValue(positionStandNoteCounts[lowestPosition].Key);
                var lowestCount = positionStandNoteCounts[lowestPosition].Value;
                var highestInputCount = lowestCount;
                var i = 1;
                while (i < positionStandNoteCounts.Length)
                {
                    var positionStandNoteCount = positionStandNoteCounts[i];
                    if (GetWaitValue(positionStandNoteCount.Key) - lowestWait < 1000.0)
                    {
                        highestInputCount += positionStandNoteCount.Value;
                        ++i;
                    }
                    else
                    {
                        targetComputing.HighestInputCount = Math.Max(highestInputCount, targetComputing.HighestInputCount);
                        highestInputCount -= lowestCount;
                        ++lowestPosition;
                        lowestWait = GetWaitValue(positionStandNoteCounts[lowestPosition].Key);
                        lowestCount = positionStandNoteCounts[lowestPosition].Value;
                    }
                }
                targetComputing.HighestInputCount = Math.Max(highestInputCount, targetComputing.HighestInputCount);
            }
            targetComputing.PlatformText = Utility.GetPlatformText(targetComputing.Title, targetComputing.Artist, targetComputing.GenreText, targetComputing.LevelText);
            targetComputing.AssistFileName = Path.GetFileName(Utility.GetFiles(NoteFile.EntryItem.EntryPath).FirstOrDefault(filePath => filePath.IsTailCaselsss(".txt") && filePath.ContainsCaselsss("README")));
        }

        public void Compile(DefaultCompute defaultComputer, bool loadParallelItems)
        {
            try
            {
                if (NoteFile.IsValid())
                {
                    defaultComputer.NoteFileContents = NoteFile.GetContents();
                    HandleCompile(defaultComputer, defaultComputer.NoteFileContents, defaultComputer.ModeComponentValue.Salt);
                    HandleCompile(defaultComputer, defaultComputer.NoteFileContents, loadParallelItems);
                    OnCompiled(defaultComputer);
                    defaultComputer.OnCompiled();
                }
                else
                {
                    throw new InvalidOperationException(LanguageSystem.Instance.EditedNoteFileFault);
                }
            }
            catch (OperationCanceledException)
            {
                defaultComputer.OnStopped();
            }
            catch (ScriptRuntimeException e)
            {
                defaultComputer.OnFault(e);
            }
            catch (Exception e)
            {
                defaultComputer.OnFault(e);
            }
            finally
            {
                defaultComputer.SetCompilingStatus(0.0);
            }
        }

        void HandleCompile(DefaultCompute defaultComputer, byte[] noteFileContents, bool loadParallelItems)
        {
            CompileImpl(defaultComputer, noteFileContents, loadParallelItems);
            if (defaultComputer.IsLongNoteStand1)
            {
                defaultComputer.TotalNotes = Notes.Sum(note => note.HasStand ? 1 : 0);
            }
            else
            {
                defaultComputer.TotalNotes = Notes.Sum(note => note.HasStand ? note.LongWait > 0.0 ? 2 : 1 : 0);
            }

            if (defaultComputer.LoopingBanalMedia != null)
            {
                defaultComputer.WaitMediaNoteMap.NewValue(0.0, new MediaNote
                {
                    MediaMode = MediaNote.Mode.Default,
                    MediaItem = defaultComputer.LoopingBanalMedia,
                    HasContents = true
                });
            }
            if (defaultComputer.LoopingBanalFailedMedia != null)
            {
                defaultComputer.WaitMediaNoteMap.NewValue(0.0, new MediaNote
                {
                    MediaMode = MediaNote.Mode.Failed,
                    MediaItem = defaultComputer.LoopingBanalFailedMedia,
                    HasContents = true
                });
            }

            // 특정 도전 과제용 라이프 게이지를 사용합니다.
            if (defaultComputer.ModeComponentValue.HitPointsModeValue == ModeComponent.HitPointsMode.Test)
            {
                defaultComputer.HitPointsValue = 0.001;
            }

            // 1P를 2P로 복사합니다.
            if (defaultComputer.ModeComponentValue.PutCopyNotesAvailable)
            {
                switch (InputMode)
                {
                    case Component.InputMode.InputMode51:
                        switch (defaultComputer.ModeComponentValue.PutCopyNotesValueV2)
                        {
                            case ModeComponent.PutCopyNotes.Copy:
                                for (var i = Notes.Count - 1; i >= 0; --i)
                                {
                                    var targetNote = CopyNote(Notes[i]);
                                    if (targetNote != null)
                                    {
                                        if (targetNote.LevyingInput == 1)
                                        {
                                            targetNote.LevyingInput = 12;
                                        }
                                        else
                                        {
                                            targetNote.LevyingInput += 5;
                                        }
                                        Notes.Add(targetNote);
                                    }
                                }
                                break;
                            case ModeComponent.PutCopyNotes.P1Symmetric:
                                for (var i = Notes.Count - 1; i >= 0; --i)
                                {
                                    var note = Notes[i];
                                    var targetNote = CopyNote(note);
                                    if (targetNote != null)
                                    {
                                        if (note.LevyingInput != 1)
                                        {
                                            note.LevyingInput = 8 - note.LevyingInput;
                                        }

                                        if (targetNote.LevyingInput == 1)
                                        {
                                            targetNote.LevyingInput = 12;
                                        }
                                        else
                                        {
                                            targetNote.LevyingInput += 5;
                                        }
                                        Notes.Add(targetNote);
                                    }
                                }
                                break;
                            case ModeComponent.PutCopyNotes.P2Symmetric:
                                for (var i = Notes.Count - 1; i >= 0; --i)
                                {
                                    var targetNote = CopyNote(Notes[i]);
                                    if (targetNote != null)
                                    {
                                        if (targetNote.LevyingInput == 1)
                                        {
                                            targetNote.LevyingInput = 12;
                                        }
                                        else
                                        {
                                            targetNote.LevyingInput = 13 - targetNote.LevyingInput;
                                        }
                                        Notes.Add(targetNote);
                                    }
                                }
                                break;
                        }
                        InputMode = Component.InputMode.InputMode102;
                        break;
                    case Component.InputMode.InputMode71:
                        switch (defaultComputer.ModeComponentValue.PutCopyNotesValueV2)
                        {
                            case ModeComponent.PutCopyNotes.Copy:
                                for (var i = Notes.Count - 1; i >= 0; --i)
                                {
                                    var targetNote = CopyNote(Notes[i]);
                                    if (targetNote != null)
                                    {
                                        if (targetNote.LevyingInput == 1)
                                        {
                                            targetNote.LevyingInput = 16;
                                        }
                                        else
                                        {
                                            targetNote.LevyingInput += 7;
                                        }
                                        Notes.Add(targetNote);
                                    }
                                }
                                break;
                            case ModeComponent.PutCopyNotes.P1Symmetric:
                                for (var i = Notes.Count - 1; i >= 0; --i)
                                {
                                    var note = Notes[i];
                                    var targetNote = CopyNote(note);
                                    if (targetNote != null)
                                    {
                                        if (note.LevyingInput != 1)
                                        {
                                            note.LevyingInput = 10 - note.LevyingInput;
                                        }

                                        if (targetNote.LevyingInput == 1)
                                        {
                                            targetNote.LevyingInput = 16;
                                        }
                                        else
                                        {
                                            targetNote.LevyingInput += 7;
                                        }
                                        Notes.Add(targetNote);
                                    }
                                }
                                break;
                            case ModeComponent.PutCopyNotes.P2Symmetric:
                                for (var i = Notes.Count - 1; i >= 0; --i)
                                {
                                    var targetNote = CopyNote(Notes[i]);
                                    if (targetNote != null)
                                    {
                                        if (targetNote.LevyingInput == 1)
                                        {
                                            targetNote.LevyingInput = 16;
                                        }
                                        else
                                        {
                                            targetNote.LevyingInput = 17 - targetNote.LevyingInput;
                                        }
                                        Notes.Add(targetNote);
                                    }
                                }
                                break;
                        }
                        InputMode = Component.InputMode.InputMode142;
                        break;
                }

                BaseNote CopyNote(BaseNote note) => note switch
                {
                    TrapNote trapNote => new TrapNote(trapNote.LogicalY, trapNote.Wait, trapNote.AudioNotes, trapNote.LevyingInput),
                    LongNote longNote => new LongNote(longNote.LogicalY, longNote.Wait, longNote.AudioNotes, longNote.LevyingInput, longNote.LongWait, longNote.LongHeight),
                    VoidNote voidNote => new VoidNote(voidNote.LogicalY, voidNote.Wait, voidNote.AudioNotes, voidNote.LevyingInput),
                    InputNote inputNote => new InputNote(inputNote.LogicalY, inputNote.Wait, inputNote.AudioNotes, inputNote.LevyingInput),
                    _ => null
                };
            }

            var levyingInputMode = InputMode;
            var inputFavorModeValue = defaultComputer.ModeComponentValue.InputFavorModeValue;
            if (inputFavorModeValue != ModeComponent.InputFavorMode.Default)
            {
                var defaultFavorInputs = Component.FavorInputs[(int)InputMode, (int)inputFavorModeValue];
                for (var i = Notes.Count - 1; i >= 0; --i)
                {
                    var note = Notes[i];
                    if (note.HasInput)
                    {
                        var input = defaultFavorInputs[note.LevyingInput];
                        if (input > 0)
                        {
                            note.LevyingInput = input;
                        }
                        else
                        {
                            Notes.RemoveAt(i);
                            foreach (var audioNote in note.AudioNotes)
                            {
                                defaultComputer.WaitAudioNoteMap.NewValue(note.Wait, audioNote);
                            }
                        }
                    }
                }
                switch (inputFavorModeValue)
                {
                    case ModeComponent.InputFavorMode.Mode4:
                        InputMode = Component.InputMode.InputMode4;
                        break;
                    case ModeComponent.InputFavorMode.Mode5:
                        InputMode = Component.InputMode.InputMode5;
                        break;
                    case ModeComponent.InputFavorMode.Mode6:
                        InputMode = Component.InputMode.InputMode6;
                        break;
                    case ModeComponent.InputFavorMode.Mode7:
                        InputMode = Component.InputMode.InputMode7;
                        break;
                    case ModeComponent.InputFavorMode.Mode8:
                        InputMode = Component.InputMode.InputMode8;
                        break;
                    case ModeComponent.InputFavorMode.Mode9:
                        InputMode = Component.InputMode.InputMode9;
                        break;
                    case ModeComponent.InputFavorMode.Mode51:
                        InputMode = Component.InputMode.InputMode51;
                        break;
                    case ModeComponent.InputFavorMode.Mode71:
                        InputMode = Component.InputMode.InputMode71;
                        break;
                    case ModeComponent.InputFavorMode.Mode102:
                        InputMode = Component.InputMode.InputMode102;
                        break;
                    case ModeComponent.InputFavorMode.Mode142:
                        InputMode = Component.InputMode.InputMode142;
                        break;
                    case ModeComponent.InputFavorMode.Mode10:
                        InputMode = Component.InputMode.InputMode10;
                        break;
                    case ModeComponent.InputFavorMode.Mode242:
                        InputMode = Component.InputMode.InputMode242;
                        break;
                    case ModeComponent.InputFavorMode.Mode484:
                        InputMode = Component.InputMode.InputMode484;
                        break;
                }
            }

            var inputCount = Component.InputCounts[(int)InputMode];
            var autoableInputCount = Component.AutoableInputCounts[(int)InputMode];
            var autoableInputs = Component.AutoableInputs[(int)InputMode];
            var isCounterWave = defaultComputer.ModeComponentValue.WaveModeValue == ModeComponent.WaveMode.Counter;

            foreach (var (wait, bpm) in WaitBPMMap)
            {
                defaultComputer.WaitBPMMap[wait] = bpm;
            }
            foreach (var (wait, stop) in WaitStopMap)
            {
                var lastBPMs = defaultComputer.WaitBPMMap.Where(pair => pair.Key <= wait).ToArray();
                var lastBPM = lastBPMs.Length > 0 ? lastBPMs.Last().Value : defaultComputer.LevyingBPM;
                ComponentValue.SetBPM(lastBPM);
                defaultComputer.WaitBPMMap[wait] = 0.0;
                defaultComputer.WaitBPMMap[wait + ComponentValue.MillisMeter * stop] = lastBPM;
            }
            var lastWait = double.MaxValue;
            foreach (var (wait, multiplier) in WaitMultiplierMap)
            {
                var lastBPMs = defaultComputer.WaitBPMMap.Where(pair => pair.Key <= wait).ToArray();
                defaultComputer.WaitBPMMap[wait] = lastBPMs.Length > 0 ? lastBPMs.Last().Value : defaultComputer.LevyingBPM;
                foreach (var targetWait in defaultComputer.WaitBPMMap.Keys.Where(targetWait => wait <= targetWait && targetWait < lastWait).ToArray())
                {
                    defaultComputer.WaitBPMMap[targetWait] *= multiplier;
                }
                lastWait = wait;
            }

            switch (defaultComputer.ModeComponentValue.BPMModeValue)
            {
                case ModeComponent.BPMMode.Not:
                    defaultComputer.WaitBPMMap.Clear();
                    defaultComputer.LevyingBPM = defaultComputer.BPM;
                    ComponentValue = new(defaultComputer.LevyingBPM);
                    foreach (var note in Notes)
                    {
                        note.LogicalY = ComponentValue.LevyingHeight - note.Wait * ComponentValue.LogicalYMillis;
                        note.LongHeight = note.LongWait * ComponentValue.LogicalYMillis;
                    }
                    break;
            }

            var audioNoteSaltComputer = new Random(defaultComputer.ModeComponentValue.Salt);
            foreach (var (wait, audioNotes) in defaultComputer.WaitAudioNoteMap.ToArray())
            {
                defaultComputer.WaitAudioNoteMap[wait] = audioNotes.Select(audioNote => new AudioNote()
                {
                    AudioItem = audioNote.AudioItem,
                    Length = audioNote.Length,
                    AudioLevyingPosition = audioNote.AudioLevyingPosition,
                    Salt = audioNoteSaltComputer.Next()
                }).ToList();
            }

            Notes.Sort();
            var inputNotes = Notes.Where(note => note.HasInput).ToArray();
            if (inputNotes.Length > 0)
            {
                var saltComputer = new Random(defaultComputer.ModeComponentValue.Salt);
                foreach (var inputNote in inputNotes)
                {
                    inputNote.Salt = saltComputer.Next();
                }
                var inputSaltComputer = new Random(defaultComputer.ModeComponentValue.Salt);
                var inputSalts = new int[inputCount + 1];
                for (var i = inputCount; i >= 0; --i)
                {
                    inputSalts[i] = inputSaltComputer.Next();
                }
                foreach (var toSaltNote in inputNotes)
                {
                    toSaltNote.InputSalt = inputSalts[toSaltNote.LevyingInput];
                }
                if (defaultComputer.ModeComponentValue.NoteSaltModeValue != ModeComponent.NoteSaltMode.Default)
                {
                    var saltInputComputer = new Random(defaultComputer.ModeComponentValue.Salt);
                    var isHalfSalt = defaultComputer.ModeComponentValue.NoteSaltModeValue == ModeComponent.NoteSaltMode.HalfInputSalt;
                    switch (defaultComputer.ModeComponentValue.NoteSaltModeValue)
                    {
                        case ModeComponent.NoteSaltMode.InputSalt:
                        case ModeComponent.NoteSaltMode.HalfInputSalt:
                            SaltInput(inputNotes);
                            break;
                        case ModeComponent.NoteSaltMode.MeterSalt:
                            var meterWaitCount = defaultComputer.MeterWaitMap.Count;
                            var levyingMeterWait = defaultComputer.MeterWaitMap[0];
                            for (var i = 1; i < meterWaitCount; ++i)
                            {
                                var asMeterWait = defaultComputer.MeterWaitMap[i];
                                var inputNoteInMeters = inputNotes.Where(note =>
                                {
                                    var wait = note.Wait;
                                    return levyingMeterWait <= wait && wait < asMeterWait;
                                }).ToArray();
                                if (inputNoteInMeters.Length > 0 && asMeterWait <= inputNoteInMeters.Max(note => note.Wait + note.LongWait))
                                {
                                    SaltInput(Array.Empty<BaseNote>());
                                    continue;
                                }
                                SaltInput(inputNotes.Where(note =>
                                {
                                    var wait = note.Wait;
                                    return levyingMeterWait <= wait && wait < asMeterWait;
                                }).ToArray());
                                levyingMeterWait = asMeterWait;
                                defaultComputer.SetCompilingStatus((double)i / (meterWaitCount - 1));
                            }
                            break;
                        default:
                            var endStatus = inputNotes.Length;
                            var status = 0;
                            var saltedNotes = new List<BaseNote>();
                            foreach (var targetNote in inputNotes)
                            {
                                var input = targetNote.LevyingInput;
                                switch (defaultComputer.ModeComponentValue.NoteSaltModeValue)
                                {
                                    case ModeComponent.NoteSaltMode.Symmetric:
                                        if (!autoableInputs.Contains(input))
                                        {
                                            targetNote.LevyingInput = autoableInputCount switch
                                            {
                                                0 => inputCount + 1 - input,
                                                1 => inputCount + 2 - input,
                                                2 => inputCount + 1 - input,
                                                4 => inputCount + 1 - input,
                                                _ => default
                                            };
                                        }
                                        break;
                                    case ModeComponent.NoteSaltMode.Salt:
                                        if (!autoableInputs.Contains(input))
                                        {
                                            targetNote.LevyingInput = autoableInputCount switch
                                            {
                                                0 => SaltNote(saltedNotes, targetNote, 1, inputCount + 1),
                                                1 => SaltNote(saltedNotes, targetNote, 2, inputCount + 1),
                                                2 => SaltNote(saltedNotes, targetNote, 2, inputCount),
                                                4 => SaltNote(saltedNotes, targetNote, 3, inputCount - 1),
                                                _ => default
                                            };
                                        }
                                        break;
                                }
                                defaultComputer.SetCompilingStatus((double)++status / endStatus);
                            }
                            break;
                    }
                    // 랜덤
                    int SaltNote(ICollection<BaseNote> saltedNotes, BaseNote targetNote, int levyingInput, int lastInput)
                    {
                        var wait = targetNote.Wait;
                        var longWait = targetNote.LongWait;
                        var levyingSaltInput = targetNote.Salt % (lastInput - levyingInput) + levyingInput;
                        var saltInput = levyingSaltInput;
                        var loopCount = lastInput - levyingInput;
                        do
                        {
                            if (saltedNotes.Any(note =>
                            {
                                return note != targetNote && note.LevyingInput == saltInput && note.IsCollided(targetNote);
                            }))
                            {
                                if (++saltInput >= lastInput)
                                {
                                    saltInput = levyingInput;
                                }
                                if (--loopCount > 0)
                                {
                                    continue;
                                }
                            }
                            break;
                        } while (true);
                        saltedNotes.Add(targetNote);
                        return saltInput;
                    }
                    // 라인 랜덤
                    void SaltInput(BaseNote[] notes)
                    {
                        var inputCountLength = inputCount - autoableInputCount;
                        var saltedInputs = new int[inputCountLength];
                        for (var i = inputCountLength - 1; i >= 0; --i)
                        {
                            switch (autoableInputCount)
                            {
                                case 0:
                                    saltedInputs[i] = i + 1;
                                    break;
                                case 1:
                                case 2:
                                    saltedInputs[i] = i + 2;
                                    break;
                            }
                        }
                        if (isHalfSalt)
                        {
                            var endHalfInputCountLength = (int)Math.Ceiling(inputCountLength / 2.0);
                            for (var i = inputCountLength - 1; i > endHalfInputCountLength; --i)
                            {
                                var j = endHalfInputCountLength + saltInputComputer.Next(inputCountLength - endHalfInputCountLength);
                                (saltedInputs[i], saltedInputs[j]) = (saltedInputs[j], saltedInputs[i]);
                            }
                            var levyingHalfInputCountLength = inputCountLength / 2;
                            for (var i = levyingHalfInputCountLength - 1; i > 0; --i)
                            {
                                var j = saltInputComputer.Next(levyingHalfInputCountLength);
                                (saltedInputs[i], saltedInputs[j]) = (saltedInputs[j], saltedInputs[i]);
                            }
                        }
                        else
                        {
                            for (var i = inputCountLength - 1; i > 0; --i)
                            {
                                var j = saltInputComputer.Next(inputCountLength);
                                (saltedInputs[i], saltedInputs[j]) = (saltedInputs[j], saltedInputs[i]);
                            }
                        }
                        foreach (var note in notes)
                        {
                            var input = note.LevyingInput;
                            if (!autoableInputs.Contains(input))
                            {
                                note.LevyingInput = autoableInputCount switch
                                {
                                    0 => saltedInputs[input - 1],
                                    1 => saltedInputs[input - 2],
                                    2 => saltedInputs[input - 2],
                                    _ => default
                                };
                            }
                        }
                    }
                }
            }

            var waitAudioNotes = defaultComputer.WaitAudioNoteMap.Where(pair => pair.Value.Count > 0).ToArray();
            var notes = Notes.Where(note => note.HasContents).ToArray();
            defaultComputer.Length = Utility.Max(notes.Select(note => note.Wait + note.LongWait).DefaultIfEmpty(0.0).Max(),
                    waitAudioNotes.Length > 0 ? waitAudioNotes.Max(pair => pair.Key) : 0.0,
                    defaultComputer.WaitMediaNoteMap.Count > 0 ? defaultComputer.WaitMediaNoteMap.Keys.Max() : 0.0
            );

            var lastLevyingPosition = defaultComputer.Length;
            var lastLevyingPositions = new double[5];
            for (var i = lastLevyingPositions.Length - 1; i >= 0; --i)
            {
                lastLevyingPositions[i] = lastLevyingPosition;
            }
            foreach (var (wait, mediaNotes) in defaultComputer.WaitMediaNoteMap)
            {
                foreach (var mediaNote in defaultComputer.WaitMediaNoteMap[wait])
                {
                    var mediaItem = mediaNote.MediaItem;
                    mediaNote.Length = mediaItem == null || double.IsInfinity(mediaItem.Length) ? lastLevyingPositions[(int)mediaNote.MediaMode] - wait : mediaItem.Length;
                    lastLevyingPositions[(int)mediaNote.MediaMode] = wait;
                }
            }

            defaultComputer.AudioLength = Utility.Max(notes.Select(note =>
            {
                var audioNotes = note.AudioNotes;
                if (audioNotes.Count > 0)
                {
                    return note.Wait + note.LongWait + audioNotes.Max(audioNote => audioNote.Length ?? audioNote.AudioItem?.Length ?? 0.0);
                }
                else
                {
                    return note.Wait + note.LongWait;
                }
            }).DefaultIfEmpty(0.0).Max(),
                waitAudioNotes.Length > 0 ? waitAudioNotes.Max(pair => pair.Key + pair.Value.Max(audioNote => audioNote.Length ?? audioNote.AudioItem?.Length ?? 0.0)) : 0.0,
                defaultComputer.WaitMediaNoteMap.Count > 0 ? defaultComputer.WaitMediaNoteMap.Max(pair => pair.Key + pair.Value.Max(mediaNote => mediaNote.Length)) : 0.0
            );

            // 거꾸로 플레이합니다.
            if (isCounterWave)
            {
                foreach (var note in Notes)
                {
                    note.Wait = defaultComputer.Length + Component.QuitWait - note.Wait - note.LongWait + Component.QuitWait;
                }

                var waitAudioNoteMap = new SortedDictionary<double, List<AudioNote>>(defaultComputer.WaitAudioNoteMap);
                defaultComputer.WaitAudioNoteMap.Clear();
                foreach (var wait in waitAudioNoteMap.Keys)
                {
                    var audioNotes = waitAudioNoteMap[wait];
                    foreach (var audioNote in audioNotes)
                    {
                        Utility.NewValue(defaultComputer.WaitAudioNoteMap, defaultComputer.Length + Component.QuitWait - wait + Component.QuitWait - (audioNote.AudioItem?.Length ?? 0.0), audioNote);
                    }
                }

                var waitMediaNoteMap = new SortedDictionary<double, List<MediaNote>>(defaultComputer.WaitMediaNoteMap);
                defaultComputer.WaitMediaNoteMap.Clear();
                foreach (var wait in waitMediaNoteMap.Keys)
                {
                    var mediaNotes = waitMediaNoteMap[wait];
                    foreach (var mediaNote in mediaNotes)
                    {
                        Utility.NewValue(defaultComputer.WaitMediaNoteMap, defaultComputer.Length + Component.QuitWait - wait + Component.QuitWait - mediaNote.Length, mediaNote);
                    }
                }

                var lastBPM = defaultComputer.LevyingBPM;
                var waitBPMMap = new Queue<KeyValuePair<double, double>>(defaultComputer.WaitBPMMap);
                defaultComputer.WaitBPMMap.Clear();
                foreach (var (wait, bpm) in waitBPMMap)
                {
                    defaultComputer.WaitBPMMap[defaultComputer.Length - wait] = lastBPM;
                    lastBPM = bpm;
                }
                defaultComputer.LevyingBPM = lastBPM;

                SetWaitLogicalYMap();

                foreach (var note in Notes)
                {
                    note.LogicalY = defaultComputer.WaitLogicalYMap[note.Wait];
                    note.LongHeight = note.LongWait > 0.0 ? defaultComputer.WaitLogicalYMap[note.Wait] - defaultComputer.WaitLogicalYMap[note.Wait + note.LongWait] : 0.0;
                }
            }
            else
            {
                SetWaitLogicalYMap();
            }
            void SetWaitLogicalYMap()
            {
                var loopingCounterSet = new SortedSet<double>();
                var waitBPMMap = new Queue<KeyValuePair<double, double>>(defaultComputer.WaitBPMMap);
                foreach (var note in Notes)
                {
                    var wait = note.Wait;
                    loopingCounterSet.Add(wait);
                    var longWait = note.LongWait;
                    if (longWait > 0.0)
                    {
                        loopingCounterSet.Add(wait + longWait);
                    }
                }
                foreach (var wait in defaultComputer.WaitAudioNoteMap.Keys)
                {
                    loopingCounterSet.Add(wait);
                }
                foreach (var wait in defaultComputer.WaitMediaNoteMap.Keys)
                {
                    loopingCounterSet.Add(wait);
                }
                var quitLength = defaultComputer.Length + Component.QuitWait;
                for (var loopingCounter = -Component.LevyingWait; loopingCounter <= quitLength; loopingCounter += 1.0)
                {
                    SetCancelCompiler?.Token.ThrowIfCancellationRequested();
                    loopingCounterSet.Add(loopingCounter);
                }

                defaultComputer.WaitLogicalYMap.Clear();
                var lastLoopingCounter = loopingCounterSet.Min();
                ComponentValue.SetBPM(defaultComputer.LevyingBPM);
                defaultComputer.WaitLogicalYMap[lastLoopingCounter] = Component.StandardHeight - ComponentValue.LogicalYMillis * (lastLoopingCounter + Component.LevyingWait);
                foreach (var loopingCounter in loopingCounterSet)
                {
                    SetCancelCompiler?.Token.ThrowIfCancellationRequested();
                    defaultComputer.WaitLogicalYMap[loopingCounter] = defaultComputer.WaitLogicalYMap[lastLoopingCounter] - Utility.GetDistance(ComponentValue, waitBPMMap, lastLoopingCounter, loopingCounter, out _);
                    lastLoopingCounter = loopingCounter;
                }
            }

            // 배경음을 노트로 만듭니다.
            Notes.Sort();
            var setNoteModeValue = defaultComputer.ModeComponentValue.SetNoteModeValue;
            if (setNoteModeValue == ModeComponent.SetNoteMode.Put || setNoteModeValue == ModeComponent.SetNoteMode.VoidPut)
            {
                var voidPutInputs = Component.VoidPutInputs[(int)levyingInputMode, (int)InputMode];
                var putNoteSet = defaultComputer.ModeComponentValue.PutNoteSet;
                var putNoteSetMillis = defaultComputer.ModeComponentValue.PutNoteSetMillis;
                var endStatus = defaultComputer.WaitAudioNoteMap.Count;
                var status = 0;
                foreach (var (wait, audioNotes) in defaultComputer.WaitAudioNoteMap)
                {
                    foreach (var audioNote in audioNotes.ToArray())
                    {
                        if (audioNote.Salt % 100 < putNoteSet)
                        {
                            switch (setNoteModeValue)
                            {
                                case ModeComponent.SetNoteMode.Put:
                                    switch (autoableInputCount)
                                    {
                                        case 0:
                                            NewNote(audioNote, GetInputs(1, inputCount));
                                            break;
                                        case 1:
                                            NewNote(audioNote, GetInputs(2, inputCount));
                                            break;
                                        case 2:
                                            NewNote(audioNote, GetInputs(2, inputCount - 1));
                                            break;
                                    }
                                    break;
                                case ModeComponent.SetNoteMode.VoidPut:
                                    NewNote(audioNote, voidPutInputs);
                                    break;
                            }
                            int[] GetInputs(int levyingInput, int lastInput)
                            {
                                var inputs = new int[lastInput - levyingInput + 1];
                                for (var i = lastInput; i >= levyingInput; --i)
                                {
                                    inputs[i - levyingInput] = i;
                                }
                                return inputs;
                            }
                            void NewNote(AudioNote audioNote, int[] inputs)
                            {
                                if (inputs.Length > 0)
                                {
                                    var levyingPutInput = audioNote.Salt % inputs.Length;
                                    var putInput = levyingPutInput;
                                    var targetInput = inputs[putInput];
                                    do
                                    {
                                        if (Notes.Any(note =>
                                        {
                                            var noteWait = note.Wait;
                                            return note.HasInput && note.LevyingInput == targetInput && noteWait - putNoteSetMillis <= wait && wait <= noteWait + putNoteSetMillis + note.LongWait;
                                        }))
                                        {
                                            if (++putInput >= inputs.Length)
                                            {
                                                putInput = 0;
                                            }
                                            if (putInput == levyingPutInput)
                                            {
                                                break;
                                            }
                                            targetInput = inputs[putInput];
                                            continue;
                                        }
                                        Notes.Add(new InputNote(defaultComputer.WaitLogicalYMap[wait], wait, new[] { audioNote }, targetInput));
                                        audioNotes.Remove(audioNote);
                                        break;
                                    } while (true);
                                }
                            }
                        }
                    }
                    defaultComputer.SetCompilingStatus((double)++status / endStatus);
                }
            }

            Notes.Sort();
            switch (defaultComputer.ModeComponentValue.NoteModifyModeValue)
            {
                // 롱 노트를 단일 노트로 만듭니다.
                case ModeComponent.NoteModifyMode.InputNote:
                    for (var i = Notes.Count - 1; i >= 0; --i)
                    {
                        var note = Notes[i];
                        if (note.LongWait > 0.0)
                        {
                            Notes[i] = new InputNote(note.LogicalY, note.Wait, note.AudioNotes, note.LevyingInput);
                        }
                    }
                    break;
                // 단일 노트를 롱 노트로 만듭니다.
                case ModeComponent.NoteModifyMode.LongNote:
                    var waitBPMMap = new Queue<KeyValuePair<double, double>>(defaultComputer.WaitBPMMap);
                    var lowestLongNoteModify = defaultComputer.ModeComponentValue.LowestLongNoteModify;
                    var highestLongNoteModify = defaultComputer.ModeComponentValue.HighestLongNoteModify;
                    var distanceLongNoteModify = highestLongNoteModify - lowestLongNoteModify;
                    for (var i = inputCount; i > 0; --i)
                    {
                        var targetInputNotes = Notes.Where(note => note.LevyingInput == i).ToArray();
                        var targetInputNoteCount = targetInputNotes.Length;
                        for (var j = 0; j < targetInputNoteCount; ++j)
                        {
                            var targetInputNote = targetInputNotes[j];
                            if (j < targetInputNoteCount - 1 && targetInputNote.LongWait == 0.0 && targetInputNote.HasStand)
                            {
                                var longNoteModify = distanceLongNoteModify > 0.0 ? lowestLongNoteModify + targetInputNote.Salt % distanceLongNoteModify : lowestLongNoteModify;
                                var targetNote = targetInputNotes[j + 1];
                                var noteWait = targetInputNote.Wait;
                                var targetLoopingCounter = targetNote.Wait;
                                var loopingCounter = targetLoopingCounter - longNoteModify;
                                if (noteWait < loopingCounter)
                                {
                                    var lastBPMs = defaultComputer.WaitBPMMap.Where(pair => pair.Key <= loopingCounter).ToArray();
                                    ComponentValue.SetBPM(lastBPMs.Length > 0 ? lastBPMs.Last().Value : defaultComputer.LevyingBPM);
                                    var distance = Utility.GetDistance(ComponentValue, waitBPMMap, loopingCounter, targetLoopingCounter, out _);
                                    Notes.Remove(targetInputNote);
                                    Notes.Add(new LongNote(targetInputNote.LogicalY, noteWait, targetInputNote.AudioNotes, targetInputNote.LevyingInput, targetLoopingCounter - noteWait - longNoteModify, targetInputNote.LogicalY - targetNote.LogicalY - distance));
                                }
                            }
                        }
                    }
                    break;
            }

            // 실시간 NPS 그래프 계산
            var isAutoLongNote = defaultComputer.IsAutoLongNote;
            var waitInputNoteCountMap = new SortedDictionary<double, int>();
            var waitAutoableInputNoteCountMap = new SortedDictionary<double, int>();
            foreach (var note in Notes.Where(note => note.HasStand))
            {
                var wait = note.Wait;
                waitInputNoteCountMap[wait] = waitInputNoteCountMap.GetValueOrDefault(wait) + 1;
                var isAutoableInput = autoableInputs.Contains(note.LevyingInput);
                if (isAutoableInput)
                {
                    waitAutoableInputNoteCountMap[wait] = waitAutoableInputNoteCountMap.GetValueOrDefault(wait) + 1;
                }
                if (!isAutoLongNote)
                {
                    var longWait = note.LongWait;
                    if (longWait > 0.0)
                    {
                        waitInputNoteCountMap[wait + longWait] = waitInputNoteCountMap.GetValueOrDefault(wait + longWait) + 1;
                        if (isAutoableInput)
                        {
                            waitAutoableInputNoteCountMap[wait + longWait] = waitAutoableInputNoteCountMap.GetValueOrDefault(wait + longWait) + 1;
                        }
                    }
                }
            }
            SetInputNoteCounts(waitInputNoteCountMap, defaultComputer.InputNoteCounts);
            SetInputNoteCounts(waitAutoableInputNoteCountMap, defaultComputer.AutoableInputNoteCounts);
            void SetInputNoteCounts(SortedDictionary<double, int> waitInputNoteCountMap, List<double> inputNoteCounts)
            {
                if (waitInputNoteCountMap.Count > 0)
                {
                    var length100 = defaultComputer.Length / 100;
                    if (length100 > 0.0)
                    {
                        var length1000 = length100 / 1000;
                        var lowestWait = 0.0;
                        var inputNoteCount = 0;
                        var i = 0;
                        while (i < waitInputNoteCountMap.Count)
                        {
                            var waitInputNoteCount = waitInputNoteCountMap.ElementAt(i);
                            if (waitInputNoteCount.Key - lowestWait < length100)
                            {
                                inputNoteCount += waitInputNoteCount.Value;
                                ++i;
                            }
                            else
                            {
                                inputNoteCounts.Add(inputNoteCount / length1000);
                                inputNoteCount = 0;
                                lowestWait += length100;
                            }
                        }
                        while (lowestWait < defaultComputer.Length)
                        {
                            inputNoteCounts.Add(inputNoteCount / length1000);
                            inputNoteCount = 0;
                            lowestWait += length100;
                        }
                    }
                    else
                    {
                        inputNoteCounts.Add(waitInputNoteCountMap.Single().Value);
                    }
                }
            }

            // 건너뛰기와 하이라이트 계산
            Notes.Sort();
            inputNotes = Notes.Where(note => note.HasInput).Where(note => note.HasStand).ToArray();
            if (inputNotes.Length > 0)
            {
                defaultComputer.PassableWait = Math.Round(inputNotes.First().Wait - Component.PassableWait);
                var lastNoteWait = inputNotes.Max(note => note.Wait + note.LongWait);
                if (double.IsNaN(defaultComputer.AudioLevyingPosition))
                {
                    var targetValue = double.NegativeInfinity;
                    var audioLevyingPosition = 0.0;
                    for (var i = defaultComputer.InputNoteCounts.Count - 1; i > 0; --i)
                    {
                        var value = (defaultComputer.InputNoteCounts[i] - defaultComputer.InputNoteCounts[i - 1]) * Math.Sqrt(50 * 50 - Math.Pow(50 - i, 2));
                        if (value >= targetValue)
                        {
                            audioLevyingPosition = defaultComputer.Length * (i - 1) / 100;
                            targetValue = value;
                        }
                    }
                    defaultComputer.AudioLevyingPosition = audioLevyingPosition;
                }
            }
            else
            {
                if (double.IsNaN(defaultComputer.AudioLevyingPosition))
                {
                    defaultComputer.AudioLevyingPosition = 0.0;
                }
            }

            // 리플레이 노트
            foreach (var inputEvent in defaultComputer.Comment.Inputs.Where(inputEvent => inputEvent.Input > 0))
            {
                var wait = Utility.SetCommentWait(defaultComputer.CommentWaitDate, defaultComputer.AudioMultiplier, inputEvent.Wait);
                wait = Math.Floor(wait);
                var input = inputEvent.Input;
                input = input & 255;
                Notes.Add(new CommentNote(defaultComputer.WaitLogicalYMap[wait], wait, input));
            }

            foreach (var note in Notes)
            {
                if (note.HasStand)
                {
                    foreach (var audioNote in note.AudioNotes)
                    {
                        defaultComputer.WaitInputAudioMap.NewValue(note.Wait, audioNote);
                    }
                }
            }

            if (defaultComputer.IsPostableItemMode)
            {
                var avatarsCount = defaultComputer.AvatarsCount;
                var rateItem = defaultComputer.ValidNetMode switch
                {
                    1 => Math.Sqrt(5.0) / 100.0,
                    2 => 5.0 / 100.0,
                    3 => Math.Pow(5.0, 2) / 100.0,
                    4 => avatarsCount,
                    _ => default
                } / avatarsCount;
                foreach (var note in Notes)
                {
                    if (note.HasStand && Random.Shared.NextDouble() < rateItem)
                    {
                        note.SetItem(Random.Shared.Next(), defaultComputer.AllowedPostableItems);
                    }
                }
            }

            defaultComputer.ValidatedTotalNotes = Notes.Sum(note => note.HasStand ? note.LongWait > 0.0 ? 2 : 1 : 0);

            Notes.Sort();
            Notes.ForEach(note =>
            {
                defaultComputer.Notes.Add(note);
                note.ID = defaultComputer.Notes.Count - 1;
            });
        }
    }
}