Newer
Older
Qwilight / Qwilight / Compiler / BMSCompiler.cs
@Taehui Taehui 12 days ago 63 KB v1.16.43
using Qwilight.Compute;
using Qwilight.Note;
using Qwilight.NoteFile;
using Qwilight.Utilities;
using System.Collections.Concurrent;
using System.IO;
using System.Text;
using UtfUnknown;

namespace Qwilight.Compiler
{
    public class BMSCompiler : BaseCompiler
    {
        static readonly char[] _delimiters = [':', ' ', '.', ';'];
        static readonly Dictionary<string, int> _formatMap = new()
        {
            { "1e517d8a7f7e34e58db1c62aeaf590b7445eb56f12d976af196afb9cda98339108346cacc8939c3cb004eee75f141f1e8104fa7ee425fa95acdf705b3c8064dc:0", 949 },
            { "8247524b799f34b78ff725cd97a2775fd7aed0f8637b2ad9899dc3cb87d9559d0c1ba093671fbf451a6faed185624e1ce01cef43c07f837885489777cd9a6947:0", 949 },
            { "85b2df20ed4b96f7aa62e17fb0480e74d19720b218586f676d90031794c11dba4cba02a65c73e3ed12759a228d7ac9e1d1e1a2f30e20cc74a06c296afe8a1a4f:0", 949 },
            { "af7a5b1825e19909fcd7e03160eb0b62c1ae8368b7825c890c3c7cfc9849e7c6d37aaa45688b88f6d2659587e2a1ae73d59f0ff64427ae1a1b6283cd12fa17e8:0", 949 },
            { "86f8bc62e9b1aeee9cc86111d2411704d20f0746be9da5cbb18f201eb34887923385b55f30736886fa4bb6211b7ce4fa3b5069485eb9c6e8f71e7634d2590fee:0", 949 },
            { "09e972e16e0ab8ee1cb85e67ed6fc3d80a3e6250eeabfcd1c63f6060a14d27fa753fc7d1474daaf8abc0f220d4077533ec06214cc22d12192bfd275145485ee9:0", 949 },
            { "bfd28d1ae0e65cf0196f599936103b565f95f2ce5bc456ed358269e9d9b4c3b5a7c0fc326482336940fe40c3f73ab4e0429c41921c6c23c3c73932a751dc0938:0", 949 },
            { "7754d64f9443af0dd6aa82f79d940ac19e263086caa4d4b4be6c28b51ad80fdd07cbe0fa588fff044a5df3fd818ca7ef3fd7726131da599035544cefcacce20f:0", 949 },
            { "af492b5a23c46b31de7aa624d6348ec3dc1b64221962c1fcc2fbdc92df332ee0ad8af235bb4de842a35dd2b76882f1ac0f41b39e57c67a4f014cd851c2e33bea:0", 949 },
            { "46cdd63acf234ef9830dfcfa3c37e2cb56076672109a08c20f7009064589d2de6980ec694cac11c0ad470fb01c3dc52c280968fcdff05be239ff0eae7f1598a0:0", 949 },
            { "c7c3884ca7033c7b250658d9b18c64f6850a99c69b7b74e478c554cbefcdb3165382c8ef48f17d08765a4ba3c21a69a3e1d480f7fb25ccbde03f5ce0addd8310:0", 949 },
            { "3ad9a18d77732225061e38064b6643491c8e999f6081c308f24f4a459cce7e739ca118e371bbf7b032b42c389736e48b53a966b0b0761b67e27a52257bb142d5:0", 949 },
            { "d1fbc5e49c22c75adc263aca6ae1cf04f5f25efbdfa42256953ee7437c345524cbfa8764b9a0336cada2d0c59fa31e4f5a67af1b71a96596d7532c874c4d507c:0", 949 },
            { "7a94c200d203f690d3cc17393ec5413c5d0213a16dff4b3dad75c94e4c1e6a1295cab5c8dd875c76c97f402e541ff2ee15a95c601095622e5eb6789c67dcd071:0", 949 },
            { "5ac89954044141254dace3e8da705b3ded9b99a54e83d2e70bcd7094ea4446ab9661992669ba67350fd6384d091631bca3bdecd5683c7db2aa06fcb566fb2927:0", 949 },
            { "f404418123d17e4560e9b32d2375dc120c53b9ee850f9ebe6920949950fee3c0397d1cac13537200b0bfbcd754daa71b8db4358418c8e530a9c825fb2309b61e:0", 949 },
            { "beb151001ec1ba01bdcbc269dffb054a664cb5a5bb5979c9dfccaaab9c4fb19bda1e943919ee3cfd3fae022a81b41bffba3565acf5d6d0434dd3a7c5d49bf4c4:0", 949 },
            { "60f8b3d9a7fd626150311623d4f6a428b053eca9a9b062e38db688f65646de92ae44a10a001582e4f4a0d49bac124fd4ae1869799731e2bb1053816ad1d5e451:0", 949 },
            { "cad4905526fd2c9d76ebc0763199fda80be35eb162624290e6cc9c567446fa4eb00588a5a22d0ee2ee4e28c6c684463a172e1ba35329bafde3f201f6fc3c3bd2:0", 949 },
            { "b90e46e5bb5f52f55ac451cf7d13073019670c0dc19f27d5e31fd8d9ee8c2d088cbb1768cb42569a0007992ff512ed7b93a2b43aa5462e0284995e14c97e36a3:0", 949 },
            { "73050fa440f63c2a9f9844824a70df5226e6e0ed1a27b4e9e45b18a9e0b988697cfaf9360a4f568c121213fcd4e518acfbf60e7bb981d8302c0a90b91d990542:0", 949 },
            { "f04bb8bfbd9abd19ef6f166f9a12e750f12270e393881b18acaf902de5e3d50baa14e030a6d598789d1f491912f954ab9dfc82e565f9f7bc7fd4a2f4ea2c4269:0", 949 },
            { "2ee33d8150c20832627ca7c0ce0987ec464575414b989cb48583083707459c5dafd399f22d1f61b720290b8a77bfd26e4b3a95bcb190b3dbcafc29fa40dacf5d:0", 949 },
            { "e9762943bf3434eb1a15aab69e485ed9927c8c6ca9a1f6c7e51756f17fa191fe9a50d96fa2c2764fddb10a1bd716c7c6b0c36d1c64f9fffd94a554bb1de67b96:0", 949 }
        };
        static readonly Dictionary<string, string> _mediaMap = new()
        {
            { "f09141432f4d624126a10a502e0542d6724f1d316cf8987f556c0063be8b77e9a0fd2cd731528e09385c175110af4eabf78fc00e97d1733c4e056a887ccf31e1:0", "BGA.mpg" }
        };

        class BMSInputItem : IComparable<BMSInputItem>
        {
            public InputNote InputNote { get; init; }

            public int CompareTo(BMSInputItem other) => InputNote.CompareTo(other.InputNote);
        }

        sealed class BMSLongInputItem : BMSInputItem
        {
            public string BMSID { get; init; } = string.Empty;
        }

        class EarlyBMSInputItem : IComparable<EarlyBMSInputItem>
        {
            public double BMSPosition { get; init; }

            public int CompareTo(EarlyBMSInputItem other) => BMSPosition.CompareTo(other.BMSPosition);
        }

        sealed class EarlyBMSLongInputItem : EarlyBMSInputItem
        {
            public string BMSID { get; init; } = string.Empty;
        }

        readonly List<string> _lines = new();
        readonly SortedDictionary<double, double> _bmsPositionLogicalYMap = new();
        readonly SortedDictionary<double, double> _bmsPositionMultiplierMap = new();
        readonly Dictionary<string, double> _bmsIDStopMap = new();
        readonly Dictionary<string, double> _bmsIDMultiplierMap = new();
        readonly SortedDictionary<double, double> _bmsPositionWaitMap = new();
        bool _hasMedia;
        bool _hasFailedMedia;
        string _longNoteBMSID;
        int _highestMeter;
        bool _is4K;
        bool _is6K;

        public BMSCompiler(BMSFile bmsFile, CancellationTokenSource setCancelCompiler) : base(bmsFile, setCancelCompiler)
        {
        }

        public override double GetWaitValue(double waitPosition) => _bmsPositionWaitMap[waitPosition];

        public override void CompileImpl(Computing targetComputing, byte[] noteFileContents, int salt)
        {
            var bmsPositionStopMap = new SortedDictionary<double, double>();
            var meterMultiplierMap = new SortedDictionary<int, double>
            {
                { -1, 1.0 }
            };
            var bmsIDBPMMap = new Dictionary<string, double>();
            using (var rms = PoolSystem.Instance.GetDataFlow(noteFileContents))
            {
                var format = NoteFormatID;
                if (format == -1)
                {
                    if (!_formatMap.TryGetValue(NoteFile.GetNoteID512(), out format))
                    {
                        var formatComputer = CharsetDetector.DetectFromStream(rms).Detected;
                        rms.Position = 0;
                        format = formatComputer != null && formatComputer.Confidence >= 0.875 && formatComputer.Encoding != null ? formatComputer.Encoding.CodePage : 932;
                    }
                }
                using var sr = new StreamReader(rms, Encoding.GetEncoding(format), false);
                var saltComputer = new Random(salt);
                var lastBin = 0;
                var isValidStatement = true;
                string line;
                while ((line = sr.ReadLine()) != null)
                {
                    if (line.StartsWith('#'))
                    {
                        var lineAt1 = line[1..];
                        var delimited = lineAt1.Split(_delimiters, 2);
                        var property = delimited[0];
                        if (property.EqualsCaseless("IF"))
                        {
                            isValidStatement = lastBin == Utility.ToInt32(delimited[1]);
                            continue;
                        }
                        if (isValidStatement)
                        {
                            if (delimited.Length > 1)
                            {
                                if (property.EqualsCaseless("RANDOM"))
                                {
                                    var rValue = Utility.ToInt32(delimited[1]);
                                    lastBin = 1 + saltComputer.Next(rValue);
                                    if (rValue > 1)
                                    {
                                        targetComputing.IsSalt = true;
                                    }
                                    continue;
                                }
                            }
                            _lines.Add(line);
                        }
                        else if (property.EqualsCaseless("ELSE") || property.EqualsCaseless("ENDIF") || property.EqualsCaseless("IFEND"))
                        {
                            isValidStatement = true;
                        }
                    }
                }
            }
            foreach (var line in _lines)
            {
                var lineAt1 = line[1..];
                var delimited = lineAt1.Split(_delimiters, 2);
                if (IsMainBMSData(lineAt1))
                {
                    var property = delimited.Length > 1 ? delimited[0] : delimited[0].Substring(0, 5);
                    property = property.PadLeft(5, '0');
                    var meter = Utility.ToInt32(property.Substring(0, 3));
                    _highestMeter = Math.Max(meter, _highestMeter);
                    if (property[3] == '0' && property[4] == '2')
                    {
                        meterMultiplierMap[meter] = Utility.ToFloat64(delimited.Length > 1 ? delimited[1] : delimited[0][5..]);
                    }
                }
                else
                {
                    var property = delimited[0].Trim();
                    if (delimited.Length > 1)
                    {
                        var data = delimited[1].Trim();
                        if (property.IsFrontCaselsss("BPM"))
                        {
                            if (property.Length > 3)
                            {
                                bmsIDBPMMap[property.Substring(3, 2)] = Utility.ToFloat64(data);
                            }
                            else
                            {
                                if (Utility.ToFloat64(data, out var levyingBPM))
                                {
                                    targetComputing.LevyingBPM = levyingBPM;
                                }
                                else
                                {
                                    var delimitedData = data.Split(" ");
                                    if (delimitedData.Length > 1)
                                    {
                                        bmsIDBPMMap[delimitedData[0]] = Utility.ToFloat64(delimitedData[1]);
                                    }
                                    else
                                    {
                                        targetComputing.LevyingBPM = Utility.ToFloat64(data.Split('-')[0]);
                                    }
                                }
                            }
                        }
                        else if (property.IsFrontCaselsss("BMP"))
                        {
                            if (property == "00")
                            {
                                _hasFailedMedia = true;
                            }
                        }
                        else if (property.EqualsCaseless("LNOBJ"))
                        {
                            _longNoteBMSID = data;
                        }
                        else if (property.EqualsCaseless("LNMODE"))
                        {
                            switch (targetComputing.LongNoteModeDate)
                            {
                                case Component.LongNoteModeDate._1_0_0:
                                    targetComputing.IsAutoLongNote = false;
                                    break;
                                case Component.LongNoteModeDate._1_14_20:
                                case Component.LongNoteModeDate._1_16_4:
                                    switch (Utility.ToInt32(data))
                                    {
                                        case 1:
                                            targetComputing.IsAutoLongNote = true;
                                            break;
                                        case 2:
                                        case 3:
                                            targetComputing.IsAutoLongNote = false;
                                            break;
                                    }
                                    break;
                            }
                        }
                        else if (property.EqualsCaseless("PREVIEW"))
                        {
                            targetComputing.TrailerAudioName = data;
                        }
                    }
                    else
                    {
                        if (property.EqualsCaseless("4K"))
                        {
                            _is4K = true;
                        }
                        else if (property.EqualsCaseless("6K"))
                        {
                            _is6K = true;
                        }
                    }
                }
            }
            var earlyBMSInputItemSet = new Dictionary<string, SortedSet<EarlyBMSInputItem>>();
            var earlyBMSLongInputItemSet = new Dictionary<string, SortedSet<EarlyBMSLongInputItem>>();
            ComponentValue = new Component(targetComputing.LevyingBPM);
            var bmsPositionSet = new SortedSet<double>();
            var title = string.Empty;
            var titleAssister = string.Empty;
            var artist = string.Empty;
            var artistAssister = string.Empty;
            var audioTargets = new List<string>();
            var audioValues = new HashSet<string>();
            foreach (var line in _lines)
            {
                var lineAt1 = line[1..];
                var delimited = lineAt1.Split(_delimiters, 2);
                if (IsMainBMSData(lineAt1))
                {
                    var property = delimited.Length > 1 ? delimited[0] : delimited[0].Substring(0, 5);
                    property = property.PadLeft(5, '0');
                    var noteVariety0 = property[3];
                    var noteVariety1 = property[4];
                    var data = delimited.Length > 1 ? delimited[1] : delimited[0][5..];
                    var dataCount = data.Length - data.Length % 2;
                    var meter = Utility.ToInt32(property.Substring(0, 3));
                    for (var meterCount = 0; meterCount < dataCount; meterCount += 2)
                    {
                        var bmsID = data.Substring(meterCount, 2);
                        if (bmsID != "00")
                        {
                            var bmsPosition = (double)meterCount / dataCount + meter;
                            var rawInput = property[3..5];
                            switch (noteVariety0)
                            {
                                case '0':
                                    switch (noteVariety1)
                                    {
                                        case '1':
                                            bmsPositionSet.Add(bmsPosition);
                                            HighestPosition = Math.Max(HighestPosition, bmsPosition);
                                            break;
                                        case '4':
                                        case '7':
                                            bmsPositionSet.Add(bmsPosition);
                                            HighestPosition = Math.Max(HighestPosition, bmsPosition);
                                            _hasMedia = true;
                                            break;
                                        case '6':
                                            bmsPositionSet.Add(bmsPosition);
                                            HighestPosition = Math.Max(HighestPosition, bmsPosition);
                                            _hasMedia = true;
                                            _hasFailedMedia = true;
                                            break;
                                        case '3':
                                        case '8':
                                        case '9':
                                            bmsPositionSet.Add(bmsPosition);
                                            break;
                                    }
                                    break;
                                case '1':
                                case '2':
                                    if (noteVariety1 != '7')
                                    {
                                        if (string.IsNullOrEmpty(_longNoteBMSID))
                                        {
                                            ++targetComputing.TotalNotes;
                                            if (noteVariety1 == '6')
                                            {
                                                ++targetComputing.AutoableNotes;
                                            }
                                            PositionStandNoteCountMap[bmsPosition] = PositionStandNoteCountMap.GetValueOrDefault(bmsPosition) + 1;
                                            HighestPosition = Math.Max(HighestPosition, bmsPosition);
                                        }
                                        else
                                        {
                                            earlyBMSLongInputItemSet.NewValue(rawInput, new EarlyBMSLongInputItem
                                            {
                                                BMSPosition = bmsPosition,
                                                BMSID = bmsID
                                            });
                                        }
                                    }
                                    bmsPositionSet.Add(bmsPosition);
                                    break;
                                case '3':
                                case '4':
                                case '7':
                                case '8':
                                    bmsPositionSet.Add(bmsPosition);
                                    break;
                                case '5':
                                case '6':
                                    if (noteVariety1 != '7')
                                    {
                                        if (!string.IsNullOrEmpty(_longNoteBMSID) && _longNoteBMSID.EqualsCaseless(bmsID))
                                        {
                                            earlyBMSLongInputItemSet.NewValue(rawInput, new EarlyBMSLongInputItem
                                            {
                                                BMSPosition = bmsPosition,
                                                BMSID = bmsID
                                            });
                                        }
                                        else
                                        {
                                            earlyBMSInputItemSet.NewValue(rawInput, new EarlyBMSInputItem
                                            {
                                                BMSPosition = bmsPosition
                                            });
                                        }
                                        bmsPositionSet.Add(bmsPosition);
                                    }
                                    break;
                                case 'D':
                                case 'E':
                                    if (noteVariety1 != '7')
                                    {
                                        ++targetComputing.TrapNotes;
                                        HighestPosition = Math.Max(HighestPosition, bmsPosition);
                                        bmsPositionSet.Add(bmsPosition);
                                    }
                                    break;
                                case 'S':
                                    switch (noteVariety1)
                                    {
                                        case 'C':
                                            bmsPositionSet.Add(bmsPosition);
                                            break;
                                    }
                                    break;
                            }
                        }
                    }
                }
                else
                {
                    if (delimited.Length > 1)
                    {
                        var property = delimited[0].Trim();
                        var data = delimited[1].Trim();
                        if (property.IsFrontCaselsss("WAV"))
                        {
                            if (!string.IsNullOrEmpty(data))
                            {
                                audioTargets.Add(property.Substring(3, 2));
                            }
                        }
                        else if (property.IsFrontCaselsss("STOP"))
                        {
                            _bmsIDStopMap[property.Substring(4, 2)] = Utility.ToFloat64(data);
                        }
                        else if (property.IsFrontCaselsss("SCROLL"))
                        {
                            _bmsIDMultiplierMap[property.Substring(6, 2)] = Utility.ToFloat64(data);
                        }
                        else if (property.EqualsCaseless("TITLE"))
                        {
                            title = data;
                        }
                        else if (property.EqualsCaseless("SUBTITLE"))
                        {
                            titleAssister = data;
                        }
                        else if (property.EqualsCaseless("ARTIST"))
                        {
                            artist = data;
                        }
                        else if (property.EqualsCaseless("SUBARTIST"))
                        {
                            artistAssister = data;
                        }
                        else if (property.EqualsCaseless("GENRE"))
                        {
                            targetComputing.Genre = data;
                        }
                        else if (property.EqualsCaseless("PLAYLEVEL"))
                        {
                            if (Utility.ToFloat64(data, out var levelTextValue))
                            {
                                targetComputing.LevelTextValue = levelTextValue;
                                levelTextValue = Math.Abs(Math.Floor(levelTextValue));
                                if (levelTextValue < 100)
                                {
                                    targetComputing.LevelText = $"LV. {levelTextValue}";
                                }
                                else
                                {
                                    targetComputing.LevelText = $"LV. {levelTextValue % 100:00}";
                                }
                            }
                            else
                            {
                                targetComputing.LevelText = data;
                            }
                        }
                        else if (property.EqualsCaseless("DIFFICULTY"))
                        {
                            if (Utility.ToInt32(data, out var level))
                            {
                                targetComputing.LevelValue = (BaseNoteFile.Level)Math.Clamp(level, (int)BaseNoteFile.Level.Level0, (int)BaseNoteFile.Level.Level5);
                            }
                        }
                        else if (property.EqualsCaseless("STAGEFILE"))
                        {
                            if (string.IsNullOrEmpty(targetComputing.NoteDrawingName))
                            {
                                targetComputing.NoteDrawingName = data;
                            }
                        }
                        else if (property.EqualsCaseless("BACKBMP"))
                        {
                            if (string.IsNullOrEmpty(targetComputing.NoteDrawingName))
                            {
                                targetComputing.NoteDrawingName = data;
                            }
                        }
                        else if (property.EqualsCaseless("BANNER"))
                        {
                            targetComputing.BannerDrawingName = data;
                        }
                        else if (property.EqualsCaseless("COMMENT"))
                        {
                            targetComputing.Tag = data;
                        }
                        else if (property.EqualsCaseless("RANK"))
                        {
                            if (Utility.ToInt32(data, out var judgmentStage))
                            {
                                switch (Math.Clamp(judgmentStage, 0, 4))
                                {
                                    case 0:
                                        targetComputing.JudgmentStage = 10;
                                        break;
                                    case 1:
                                        targetComputing.JudgmentStage = 7;
                                        break;
                                    case 2:
                                        targetComputing.JudgmentStage = 5;
                                        break;
                                    case 3:
                                        targetComputing.JudgmentStage = 3;
                                        break;
                                    case 4:
                                        targetComputing.JudgmentStage = 0;
                                        break;
                                }
                            }
                        }
                    }
                }
            }
            targetComputing.Title = string.IsNullOrEmpty(titleAssister) || title.EndsWith(titleAssister) ? title : $"{title} {titleAssister}";
            targetComputing.Artist = string.IsNullOrEmpty(artistAssister) ? artist : $"{artist} / {artistAssister}";

            var isAutoLongNote = targetComputing.IsAutoLongNote;
            foreach (var (rawInput, earlyBMSLongInputItems) in earlyBMSLongInputItemSet)
            {
                EarlyBMSLongInputItem lastEarlyBMSLongInputItem = null;
                foreach (var earlyBMSLongInputItem in earlyBMSLongInputItems)
                {
                    if (earlyBMSLongInputItem.BMSID.EqualsCaseless(_longNoteBMSID))
                    {
                        if (lastEarlyBMSLongInputItem != null)
                        {
                            ++targetComputing.LongNotes;
                            targetComputing.TotalNotes += targetComputing.IsLongNoteStand1 ? 1 : 2;
                            if (rawInput[1] == '6')
                            {
                                targetComputing.AutoableNotes += targetComputing.IsLongNoteStand1 ? 1 : 2;
                            }
                            var bmsPosition = lastEarlyBMSLongInputItem.BMSPosition;
                            HighestPosition = Math.Max(HighestPosition, bmsPosition);
                            PositionStandNoteCountMap[bmsPosition] = PositionStandNoteCountMap.GetValueOrDefault(bmsPosition) + 1;
                            bmsPosition = earlyBMSLongInputItem.BMSPosition;
                            HighestPosition = Math.Max(HighestPosition, bmsPosition);
                            if (!isAutoLongNote)
                            {
                                PositionStandNoteCountMap[bmsPosition] = PositionStandNoteCountMap.GetValueOrDefault(bmsPosition) + 1;
                            }
                            lastEarlyBMSLongInputItem = null;
                            continue;
                        }
                    }
                    else
                    {
                        if (lastEarlyBMSLongInputItem != null)
                        {
                            ++targetComputing.TotalNotes;
                            if (rawInput[1] == '6')
                            {
                                ++targetComputing.AutoableNotes;
                            }
                            var bmsPosition = lastEarlyBMSLongInputItem.BMSPosition;
                            HighestPosition = Math.Max(HighestPosition, bmsPosition);
                            PositionStandNoteCountMap[bmsPosition] = PositionStandNoteCountMap.GetValueOrDefault(bmsPosition) + 1;
                        }
                    }
                    lastEarlyBMSLongInputItem = earlyBMSLongInputItem;
                }
                if (lastEarlyBMSLongInputItem != null)
                {
                    ++targetComputing.TotalNotes;
                    if (rawInput[1] == '6')
                    {
                        ++targetComputing.AutoableNotes;
                    }
                    var bmsPosition = lastEarlyBMSLongInputItem.BMSPosition;
                    HighestPosition = Math.Max(HighestPosition, bmsPosition);
                    PositionStandNoteCountMap[bmsPosition] = PositionStandNoteCountMap.GetValueOrDefault(bmsPosition) + 1;
                }
            }

            foreach (var (rawInput, earlyBMSInputItems) in earlyBMSInputItemSet)
            {
                EarlyBMSInputItem lastEarlyBMSInputItem = null;
                foreach (var earlyBMSInputItem in earlyBMSInputItems)
                {
                    if (lastEarlyBMSInputItem != null)
                    {
                        ++targetComputing.LongNotes;
                        targetComputing.TotalNotes += targetComputing.IsLongNoteStand1 ? 1 : 2;
                        if (rawInput[1] == '6')
                        {
                            targetComputing.AutoableNotes += targetComputing.IsLongNoteStand1 ? 1 : 2;
                        }
                        var bmsPosition = lastEarlyBMSInputItem.BMSPosition;
                        HighestPosition = Math.Max(HighestPosition, bmsPosition);
                        PositionStandNoteCountMap[bmsPosition] = PositionStandNoteCountMap.GetValueOrDefault(bmsPosition) + 1;
                        bmsPosition = earlyBMSInputItem.BMSPosition;
                        HighestPosition = Math.Max(HighestPosition, bmsPosition);
                        if (!isAutoLongNote)
                        {
                            PositionStandNoteCountMap[bmsPosition] = PositionStandNoteCountMap.GetValueOrDefault(bmsPosition) + 1;
                        }
                        lastEarlyBMSInputItem = null;
                    }
                    else
                    {
                        lastEarlyBMSInputItem = earlyBMSInputItem;
                    }
                }
                if (lastEarlyBMSInputItem != null)
                {
                    ++targetComputing.TotalNotes;
                    if (rawInput[1] == '6')
                    {
                        ++targetComputing.AutoableNotes;
                    }
                    var bmsPosition = lastEarlyBMSInputItem.BMSPosition;
                    HighestPosition = Math.Max(HighestPosition, bmsPosition);
                    PositionStandNoteCountMap[bmsPosition] = PositionStandNoteCountMap.GetValueOrDefault(bmsPosition) + 1;
                }
            }

            var inputSet = new HashSet<int>();
            foreach (var line in _lines)
            {
                var lineAt1 = line[1..];
                var delimited = lineAt1.Split(_delimiters, 2);
                if (IsMainBMSData(lineAt1))
                {
                    var property = delimited.Length > 1 ? delimited[0] : delimited[0].Substring(0, 5);
                    property = property.PadLeft(5, '0');
                    var data = delimited.Length > 1 ? delimited[1] : delimited[0][5..];
                    var dataCount = data.Length - data.Length % 2;
                    var meter = Utility.ToInt32(property.Substring(0, 3));
                    for (var meterCount = 0; meterCount < dataCount; meterCount += 2)
                    {
                        var bmsID = data.Substring(meterCount, 2);
                        if (bmsID != "00")
                        {
                            var bmsPosition = (double)meterCount / dataCount + meter;
                            var noteVariety0 = property[3];
                            var noteVariety1 = property[4];
                            switch (noteVariety0)
                            {
                                case '0':
                                    switch (noteVariety1)
                                    {
                                        case '3':
                                            var bpmHex = Utility.ToIntHex(bmsID);
                                            PositionBPMMap[bmsPosition] = bpmHex;
                                            PositionBPMUIMap[bmsPosition] = bpmHex;
                                            break;
                                        case '8':
                                            if (bmsIDBPMMap.TryGetValue(bmsID, out var bpm))
                                            {
                                                PositionBPMMap[bmsPosition] = bpm;
                                                PositionBPMUIMap[bmsPosition] = bpm;
                                            }
                                            break;
                                        case '9':
                                            if (_bmsIDStopMap.TryGetValue(bmsID, out var stop))
                                            {
                                                if (stop < 0.0)
                                                {
                                                    throw new ArgumentException(LanguageSystem.Instance.NegativeStopFault);
                                                }
                                                bmsPositionStopMap[bmsPosition] = stop;
                                            }
                                            break;
                                    }
                                    break;
                                case '1':
                                case '2':
                                case '5':
                                case '6':
                                    if (audioTargets.Contains(bmsID))
                                    {
                                        audioValues.Add(bmsID);
                                    }
                                    break;
                                case 'S':
                                    switch (noteVariety1)
                                    {
                                        case 'C':
                                            if (_bmsIDMultiplierMap.TryGetValue(bmsID, out var multiplier))
                                            {
                                                _bmsPositionMultiplierMap[bmsPosition] = multiplier;
                                                PositionBPMUIMap[bmsPosition] = targetComputing.LevyingBPM * multiplier;
                                            }
                                            break;
                                    }
                                    break;
                            }
                            inputSet.Add(GetBMSInput(noteVariety0, noteVariety1, Component.InputMode._14_2));
                        }
                    }
                }
                else
                {
                    if (delimited.Length > 1)
                    {
                        var property = delimited[0].Trim();
                        var data = delimited[1].Trim();
                        if (property.EqualsCaseless("TOTAL"))
                        {
                            if (Utility.ToFloat64(data, out var total))
                            {
                                if (total > 0.0)
                                {
                                    targetComputing.HitPointsValue = total / (100 * targetComputing.TotalNotes);
                                    Total = total;
                                }
                            }
                        }
                    }
                }
            }
            InputMode = GetInputMode(inputSet);

            for (var i = 0; i <= _highestMeter + 1; ++i)
            {
                bmsPositionSet.Add(i);
            }
            var lastBMSPosition = 0.0;
            var lastLogicalY = 0.0;
            var lastWait = 0.0;
            var lastMultiplier = 1.0;
            foreach (var bmsPosition in bmsPositionSet)
            {
                var meterMultiplier = meterMultiplierMap.GetValueOrDefault((int)lastBMSPosition, 1.0);
                lastLogicalY -= (bmsPosition - lastBMSPosition) * lastMultiplier * meterMultiplier * ComponentValue.LogicalYMeter;
                _bmsPositionLogicalYMap[bmsPosition] = lastLogicalY;
                lastWait += ComponentValue.MillisMeter * meterMultiplier * (bmsPosition - lastBMSPosition);
                _bmsPositionWaitMap[bmsPosition] = lastWait;
                if (PositionBPMMap.TryGetValue(bmsPosition, out var bpm))
                {
                    ComponentValue.SetBPM(bpm);
                }
                if (bmsPositionStopMap.TryGetValue(bmsPosition, out var stop))
                {
                    lastWait += ComponentValue.MillisMeter * stop / Component.StandardMeter;
                }
                if (_bmsPositionMultiplierMap.TryGetValue(bmsPosition, out var multiplier))
                {
                    lastMultiplier = multiplier;
                }
                lastBMSPosition = bmsPosition;
            }
            targetComputing.IsBanned = audioValues.Count < 2 || targetComputing.TotalNotes == 0;
            targetComputing.IsHellBPM = targetComputing.TrapNotes > 0;
        }

        public override void CompileImpl(DefaultCompute defaultComputer, byte[] noteFileContents, bool loadParallelItems)
        {
            var bmsIDAudioItemMap = new ConcurrentDictionary<string, AudioItem?>();
            var bmsIDHandledItemMap = new ConcurrentDictionary<string, IHandledItem>();
            var parallelItems = new ConcurrentBag<Action>();
            try
            {
                var noteDrawingPath = Utility.GetFilePath(defaultComputer.NoteDrawingPath, Utility.FileFormatFlag.Drawing);
                if (!string.IsNullOrEmpty(noteDrawingPath))
                {
                    defaultComputer.NoteDrawing = new HandledDrawingItem
                    {
                        Drawing = DrawingSystem.Instance.Load(noteDrawingPath, defaultComputer),
                        DefaultDrawing = DrawingSystem.Instance.LoadDefault(noteDrawingPath, defaultComputer)
                    };
                }
            }
            catch
            {
            }
            try
            {
                var bannerDrawingPath = Utility.GetFilePath(defaultComputer.BannerDrawingPath, Utility.FileFormatFlag.Drawing);
                if (!string.IsNullOrEmpty(bannerDrawingPath))
                {
                    defaultComputer.BannerDrawing = DrawingSystem.Instance.Load(bannerDrawingPath, defaultComputer);
                }
            }
            catch
            {
            }
            var isBanalMedia = (!_hasMedia || defaultComputer.AlwaysBanalMedia) && defaultComputer.BanalMedia;
            var isBanalFailedMedia = (!_hasFailedMedia || defaultComputer.AlwaysBanalFailedMedia) && defaultComputer.BanalFailedMedia;
            defaultComputer.LoadBanalMedia(isBanalMedia, isBanalFailedMedia, parallelItems);
            foreach (var line in _lines)
            {
                var lineAt1 = line[1..];
                if (!IsMainBMSData(lineAt1))
                {
                    var delimited = lineAt1.Split(_delimiters, 2);
                    var property = delimited[0].Trim();
                    if (delimited.Length > 1)
                    {
                        var data = delimited[1].Trim();
                        if (property.IsFrontCaselsss("BMP"))
                        {
                            if (defaultComputer.LoadedMedia)
                            {
                                property = property.Substring(3, 2);
                                if ((property != "00" || !isBanalFailedMedia) && !isBanalMedia)
                                {
                                    parallelItems.Add(() =>
                                    {
                                        try
                                        {
                                            if (_mediaMap.TryGetValue(NoteFile.GetNoteID512(), out var media))
                                            {
                                                data = media;
                                            }
                                            var mediaFilePath = Utility.GetFilePath(Path.Combine(NoteFile.EntryItem.EntryPath, data), Utility.FileFormatFlag.Drawing | Utility.FileFormatFlag.Media);
                                            if (!string.IsNullOrEmpty(mediaFilePath))
                                            {
                                                bmsIDHandledItemMap[property] = Utility.GetFileFormat(mediaFilePath) switch
                                                {
                                                    Utility.FileFormatFlag.Drawing => new HandledDrawingItem
                                                    {
                                                        Drawing = DrawingSystem.Instance.LoadBMS(mediaFilePath, defaultComputer),
                                                        DefaultDrawing = DrawingSystem.Instance.LoadDefaultBMS(mediaFilePath, defaultComputer)
                                                    },
                                                    Utility.FileFormatFlag.Media => MediaSystem.Instance.Load(Utility.GetFiles(Path.GetDirectoryName(mediaFilePath), $"{Path.GetFileNameWithoutExtension(mediaFilePath)}.*")
                                                        .Where(targetFile => targetFile.IsTailCaselsss(".mp4"))
                                                        .FirstOrDefault() ?? mediaFilePath, defaultComputer, false),
                                                    _ => null as IHandledItem,
                                                };
                                            }
                                        }
                                        catch
                                        {
                                        }
                                    });
                                }
                            }
                        }
                        else if (property.IsFrontCaselsss("WAV"))
                        {
                            parallelItems.Add(() =>
                            {
                                try
                                {
                                    var audioFilePath = Utility.GetFilePath(Path.Combine(NoteFile.EntryItem.EntryPath, data), Utility.FileFormatFlag.Audio);
                                    if (!string.IsNullOrEmpty(audioFilePath))
                                    {
                                        bmsIDAudioItemMap[property.Substring(3, 2)] = AudioSystem.Instance.Load(audioFilePath, defaultComputer, 1F);
                                    }
                                }
                                catch
                                {
                                }
                            });
                        }
                    }
                }
            }

            if (loadParallelItems)
            {
                var parallelItemsCount = parallelItems.Count;
                var loadedParallelItemsCount = 0;
                Utility.HandleLowestlyParallelly(parallelItems, Configure.Instance.CompilingBin, parallelItem =>
                {
                    parallelItem();
                    defaultComputer.SetCompilingStatus((double)Interlocked.Increment(ref loadedParallelItemsCount) / parallelItemsCount);
                }, SetCancelCompiler?.Token);
            }

            if (bmsIDHandledItemMap.TryGetValue("00", out var mediaItem))
            {
                defaultComputer.WaitMediaNoteMap.NewValue(0.0, new MediaNote
                {
                    MediaMode = MediaNote.Mode.Failed,
                    HasContents = defaultComputer.LoadedMedia,
                    MediaItem = mediaItem
                });
            }
            Notes.AddRange(Enumerable.Range(0, _highestMeter + 1).Select(i =>
            {
                var logicalY = ComponentValue.LevyingHeight + _bmsPositionLogicalYMap[i];
                var wait = _bmsPositionWaitMap[i];
                defaultComputer.MeterWaitMap[i] = wait;
                return new MeterNote(logicalY, wait, i);
            }));
            var inputCount = Component.InputCounts[(int)InputMode];
            var bmsInputItemSets = new SortedSet<BMSInputItem>[inputCount + 1];
            for (var i = inputCount; i > 0; --i)
            {
                bmsInputItemSets[i] = new();
            }
            var bmsLongInputItemSets = new SortedSet<BMSLongInputItem>[inputCount + 1];
            for (var i = inputCount; i > 0; --i)
            {
                bmsLongInputItemSets[i] = new();
            }
            foreach (var line in _lines)
            {
                var lineAt1 = line[1..];
                if (IsMainBMSData(lineAt1))
                {
                    var delimited = lineAt1.Split(_delimiters, 2);
                    var property = delimited.Length > 1 ? delimited[0] : delimited[0].Substring(0, 5);
                    property = property.PadLeft(5, '0');
                    var noteVariety0 = property[3];
                    var noteVariety1 = property[4];
                    if (noteVariety0 != '0' || noteVariety1 != '2')
                    {
                        var data = delimited.Length > 1 ? delimited[1] : delimited[0][5..];
                        var dataCount = data.Length - data.Length % 2;
                        var meter = Utility.ToInt32(property.Substring(0, 3));
                        for (var meterCount = 0; meterCount < dataCount; meterCount += 2)
                        {
                            var bmsID = data.Substring(meterCount, 2);
                            if (bmsID != "00")
                            {
                                var input = GetBMSInput(noteVariety0, noteVariety1, InputMode);
                                var bmsPosition = (double)meterCount / dataCount + meter;
                                var logicalY = ComponentValue.LevyingHeight + _bmsPositionLogicalYMap[bmsPosition];
                                var wait = _bmsPositionWaitMap[bmsPosition];
                                bmsIDAudioItemMap.TryGetValue(bmsID, out var audioItem);
                                var audioNote = new AudioNote
                                {
                                    AudioItem = audioItem,
                                    BMSID = bmsID
                                };
                                bmsIDHandledItemMap.TryGetValue(bmsID, out mediaItem);
                                var hasContents = defaultComputer.LoadedMedia;
                                switch (noteVariety0)
                                {
                                    case '0':
                                        switch (noteVariety1)
                                        {
                                            case '1':
                                                defaultComputer.WaitAudioNoteMap.NewValue(wait, audioNote);
                                                break;
                                            case '3':
                                            case '8':
                                                if (PositionBPMMap.TryGetValue(bmsPosition, out var bpm))
                                                {
                                                    WaitBPMMap[wait] = bpm;
                                                }
                                                break;
                                            case '4' when !isBanalMedia:
                                                defaultComputer.WaitMediaNoteMap.NewValue(wait, new MediaNote
                                                {
                                                    MediaMode = MediaNote.Mode.Default,
                                                    MediaItem = mediaItem,
                                                    HasContents = hasContents
                                                });
                                                break;
                                            case '6' when !isBanalMedia && !isBanalFailedMedia:
                                                defaultComputer.WaitMediaNoteMap.NewValue(wait, new MediaNote
                                                {
                                                    MediaMode = MediaNote.Mode.Failed,
                                                    MediaItem = mediaItem,
                                                    HasContents = hasContents
                                                });
                                                break;
                                            case '7' when !isBanalMedia:
                                                defaultComputer.WaitMediaNoteMap.NewValue(wait, new MediaNote
                                                {
                                                    MediaMode = MediaNote.Mode.Layer,
                                                    MediaItem = mediaItem,
                                                    HasContents = hasContents
                                                });
                                                break;
                                            case '9':
                                                WaitStopMap[wait] = _bmsIDStopMap.GetValueOrDefault(bmsID) / Component.StandardMeter;
                                                break;
                                        }
                                        break;
                                    case '1':
                                    case '2':
                                        if (input != default)
                                        {
                                            var inputNote = new InputNote(logicalY, wait, [audioNote], input);
                                            if (string.IsNullOrEmpty(_longNoteBMSID))
                                            {
                                                Notes.Add(inputNote);
                                            }
                                            else
                                            {
                                                bmsLongInputItemSets[input].Add(new()
                                                {
                                                    InputNote = inputNote,
                                                    BMSID = bmsID
                                                });
                                            }
                                        }
                                        break;
                                    case '3':
                                    case '4':
                                        if (input != default)
                                        {
                                            Notes.Add(new VoidNote(logicalY, wait, [audioNote], input));
                                        }
                                        break;
                                    case '5':
                                    case '6':
                                        if (input != default)
                                        {
                                            var inputNote = new InputNote(logicalY, wait, [audioNote], input);
                                            if (!string.IsNullOrEmpty(_longNoteBMSID) && _longNoteBMSID.EqualsCaseless(bmsID))
                                            {
                                                bmsLongInputItemSets[input].Add(new()
                                                {
                                                    InputNote = inputNote,
                                                    BMSID = bmsID
                                                });
                                            }
                                            else
                                            {
                                                bmsInputItemSets[input].Add(new()
                                                {
                                                    InputNote = inputNote
                                                });
                                            }
                                        }
                                        break;
                                    case 'D':
                                    case 'E':
                                        if (input != default)
                                        {
                                            Notes.Add(new TrapNote(logicalY, wait, Array.Empty<AudioNote>(), input));
                                        }
                                        break;
                                    case 'S':
                                        switch (noteVariety1)
                                        {
                                            case 'C' when _bmsPositionMultiplierMap.TryGetValue(bmsPosition, out var multiplier):
                                                WaitMultiplierMap[wait] = multiplier;
                                                break;
                                        }
                                        break;
                                }
                            }
                        }
                    }
                }
            }
            for (var i = inputCount; i > 0; --i)
            {
                BMSLongInputItem lastBMSLongInputItem = null;
                foreach (var bmsLongInputItem in bmsLongInputItemSets[i])
                {
                    if (bmsLongInputItem.BMSID.EqualsCaseless(_longNoteBMSID))
                    {
                        if (lastBMSLongInputItem != null)
                        {
                            var inputNote = lastBMSLongInputItem.InputNote;
                            var wait = inputNote.Wait;
                            Notes.Add(new LongNote(inputNote.LogicalY, wait, inputNote.AudioNotes, inputNote.LevyingInput, bmsLongInputItem.InputNote.Wait - wait, inputNote.LogicalY - bmsLongInputItem.InputNote.LogicalY));
                            lastBMSLongInputItem = null;
                            continue;
                        }
                    }
                    else
                    {
                        if (lastBMSLongInputItem != null)
                        {
                            Notes.Add(lastBMSLongInputItem.InputNote);
                        }
                    }
                    lastBMSLongInputItem = bmsLongInputItem;
                }
                if (lastBMSLongInputItem != null)
                {
                    Notes.Add(lastBMSLongInputItem.InputNote);
                }
            }
            for (var i = inputCount; i > 0; --i)
            {
                BMSInputItem lastBMSInputItem = null;
                foreach (var bmsInputItem in bmsInputItemSets[i])
                {
                    if (lastBMSInputItem != null)
                    {
                        var inputNote = lastBMSInputItem.InputNote;
                        var wait = inputNote.Wait;
                        Notes.Add(new LongNote(inputNote.LogicalY, wait, inputNote.AudioNotes, i, bmsInputItem.InputNote.Wait - wait, inputNote.LogicalY - bmsInputItem.InputNote.LogicalY));
                        lastBMSInputItem = null;
                    }
                    else
                    {
                        lastBMSInputItem = bmsInputItem;
                    }
                }
                if (lastBMSInputItem != null)
                {
                    Notes.Add(lastBMSInputItem.InputNote);
                }
            }
        }

        public virtual Component.InputMode GetInputMode(ICollection<int> inputSet)
        {
            if (_is4K)
            {
                return Component.InputMode._4;
            }
            if (_is6K)
            {
                return Component.InputMode._6;
            }
            if (inputSet.All(input => input == 0))
            {
                return Component.InputMode._5_1;
            }
            var isMode71 = inputSet.Contains(7) || inputSet.Contains(8);
            var isMode102 = inputSet.Contains(9) || inputSet.Contains(10) || inputSet.Contains(11) || inputSet.Contains(12) || inputSet.Contains(13) || inputSet.Contains(16);
            var isMode142 = (isMode71 && isMode102) || inputSet.Contains(14) || inputSet.Contains(15);
            if (isMode142)
            {
                return Component.InputMode._14_2;
            }
            if (isMode102)
            {
                return Component.InputMode._10_2;
            }
            if (isMode71)
            {
                return Component.InputMode._7_1;
            }
            return Component.InputMode._5_1;
        }

        static bool IsMainBMSData(string lineAt1)
        {
            if (lineAt1.Length >= 5)
            {
                var lineAt13 = lineAt1[3];
                var lineAt14 = lineAt1[4];
                return char.IsDigit(lineAt1[0]) && char.IsDigit(lineAt1[1]) && char.IsDigit(lineAt1[2]) && (char.IsDigit(lineAt13) || ((lineAt13 == 'D' || lineAt13 == 'E') && char.IsDigit(lineAt14)) || (lineAt13 == 'S' && lineAt14 == 'C'));
            }
            else
            {
                return false;
            }
        }

        static int GetBMSInput(char noteVariety0, char noteVariety1, Component.InputMode inputMode)
        {
            switch (noteVariety0)
            {
                case '1':
                case '3':
                case '5':
                case 'D':
                    switch (inputMode)
                    {
                        case Component.InputMode._4:
                            switch (noteVariety1)
                            {
                                case '1':
                                case '2':
                                    return noteVariety1 - '0';
                                case '4':
                                case '5':
                                    return noteVariety1 - '1';
                            }
                            break;
                        case Component.InputMode._5_1:
                        case Component.InputMode._10_2:
                            switch (noteVariety1)
                            {
                                case '1':
                                case '2':
                                case '3':
                                case '4':
                                case '5':
                                    return noteVariety1 - '0' + 1;
                                case '6':
                                    return 1;
                            }
                            break;
                        case Component.InputMode._6:
                            switch (noteVariety1)
                            {
                                case '1':
                                case '2':
                                case '3':
                                    return noteVariety1 - '0';
                                case '5':
                                    return noteVariety1 - '1';
                                case '8':
                                case '9':
                                    return noteVariety1 - '3';
                            }
                            break;
                        case Component.InputMode._7_1:
                        case Component.InputMode._14_2:
                            switch (noteVariety1)
                            {
                                case '1':
                                case '2':
                                case '3':
                                case '4':
                                case '5':
                                    return noteVariety1 - '0' + 1;
                                case '6':
                                    return 1;
                                case '8':
                                case '9':
                                    return noteVariety1 - '1';
                            }
                            break;
                        case Component.InputMode._9:
                            switch (noteVariety1)
                            {
                                case '1':
                                case '2':
                                case '3':
                                case '4':
                                case '5':
                                    return noteVariety1 - '0';
                            }
                            break;
                    }
                    break;
                case '2':
                case '4':
                case '6':
                case 'E':
                    switch (inputMode)
                    {
                        case Component.InputMode._10_2:
                            switch (noteVariety1)
                            {
                                case '1':
                                case '2':
                                case '3':
                                case '4':
                                case '5':
                                    return noteVariety1 - '0' + 6;
                                case '6':
                                    return 12;
                            }
                            break;
                        case Component.InputMode._14_2:
                            switch (noteVariety1)
                            {
                                case '1':
                                case '2':
                                case '3':
                                case '4':
                                case '5':
                                    return noteVariety1 - '0' + 8;
                                case '6':
                                    return 16;
                                case '8':
                                case '9':
                                    return noteVariety1 - '2' + 8;
                            }
                            break;
                        case Component.InputMode._9:
                            switch (noteVariety1)
                            {
                                case '2':
                                case '3':
                                case '4':
                                case '5':
                                    return noteVariety1 - '0' + 4;
                            }
                            break;
                    }
                    break;
            }
            return default;
        }
    }
}