예제 #1
0
        private void readCustomSampleBanks(string str, SampleBankInfo bankInfo)
        {
            if (string.IsNullOrEmpty(str))
            {
                return;
            }

            string[] split = str.Split(':');

            var bank    = (LegacyBeatmapDecoder.LegacySampleBank)Convert.ToInt32(split[0]);
            var addbank = (LegacyBeatmapDecoder.LegacySampleBank)Convert.ToInt32(split[1]);

            string stringBank = bank.ToString().ToLower();

            if (stringBank == @"none")
            {
                stringBank = null;
            }
            string stringAddBank = addbank.ToString().ToLower();

            if (stringAddBank == @"none")
            {
                stringAddBank = null;
            }

            bankInfo.Normal = stringBank;
            bankInfo.Add    = stringAddBank;

            if (split.Length > 3)
            {
                bankInfo.Volume = int.Parse(split[3]);
            }

            bankInfo.Filename = split.Length > 4 ? split[4] : null;
        }
예제 #2
0
        private List <SampleInfo> convertSoundType(LegacySoundType type, SampleBankInfo bankInfo)
        {
            // Todo: This should return the normal SampleInfos if the specified sample file isn't found, but that's a pretty edge-case scenario
            if (!string.IsNullOrEmpty(bankInfo.Filename))
            {
                return new List <SampleInfo> {
                           new FileSampleInfo {
                               Filename = bankInfo.Filename
                           }
                }
            }
            ;

            var soundTypes = new List <SampleInfo>
            {
                new LegacySampleInfo
                {
                    Bank             = bankInfo.Normal,
                    Name             = SampleInfo.HIT_NORMAL,
                    Volume           = bankInfo.Volume,
                    CustomSampleBank = bankInfo.CustomSampleBank
                }
            };

            if (type.HasFlag(LegacySoundType.Finish))
            {
                soundTypes.Add(new LegacySampleInfo
                {
                    Bank             = bankInfo.Add,
                    Name             = SampleInfo.HIT_FINISH,
                    Volume           = bankInfo.Volume,
                    CustomSampleBank = bankInfo.CustomSampleBank
                });
            }

            if (type.HasFlag(LegacySoundType.Whistle))
            {
                soundTypes.Add(new LegacySampleInfo
                {
                    Bank             = bankInfo.Add,
                    Name             = SampleInfo.HIT_WHISTLE,
                    Volume           = bankInfo.Volume,
                    CustomSampleBank = bankInfo.CustomSampleBank
                });
            }

            if (type.HasFlag(LegacySoundType.Clap))
            {
                soundTypes.Add(new LegacySampleInfo
                {
                    Bank             = bankInfo.Add,
                    Name             = SampleInfo.HIT_CLAP,
                    Volume           = bankInfo.Volume,
                    CustomSampleBank = bankInfo.CustomSampleBank
                });
            }

            return(soundTypes);
        }
예제 #3
0
        private SampleInfoList convertSoundType(LegacySoundType type, SampleBankInfo bankInfo)
        {
            var soundTypes = new SampleInfoList
            {
                new SampleInfo
                {
                    Bank   = bankInfo.Normal,
                    Name   = SampleInfo.HIT_NORMAL,
                    Volume = bankInfo.Volume
                }
            };

            if ((type & LegacySoundType.Finish) > 0)
            {
                soundTypes.Add(new SampleInfo
                {
                    Bank   = bankInfo.Add,
                    Name   = SampleInfo.HIT_FINISH,
                    Volume = bankInfo.Volume
                });
            }

            if ((type & LegacySoundType.Whistle) > 0)
            {
                soundTypes.Add(new SampleInfo
                {
                    Bank   = bankInfo.Add,
                    Name   = SampleInfo.HIT_WHISTLE,
                    Volume = bankInfo.Volume
                });
            }

            if ((type & LegacySoundType.Clap) > 0)
            {
                soundTypes.Add(new SampleInfo
                {
                    Bank   = bankInfo.Add,
                    Name   = SampleInfo.HIT_CLAP,
                    Volume = bankInfo.Volume
                });
            }

            return(soundTypes);
        }
예제 #4
0
        private void readCustomSampleBanks(string str, SampleBankInfo bankInfo)
        {
            if (string.IsNullOrEmpty(str))
            {
                return;
            }

            string[] split = str.Split(':');

            var bank    = (LegacySampleBank)Parsing.ParseInt(split[0]);
            var addbank = (LegacySampleBank)Parsing.ParseInt(split[1]);

            string stringBank = bank.ToString().ToLowerInvariant();

            if (stringBank == @"none")
            {
                stringBank = null;
            }
            string stringAddBank = addbank.ToString().ToLowerInvariant();

            if (stringAddBank == @"none")
            {
                stringAddBank = null;
            }

            bankInfo.Normal = stringBank;
            bankInfo.Add    = string.IsNullOrEmpty(stringAddBank) ? stringBank : stringAddBank;

            if (split.Length > 2)
            {
                bankInfo.CustomSampleBank = Parsing.ParseInt(split[2]);
            }

            if (split.Length > 3)
            {
                bankInfo.Volume = Math.Max(0, Parsing.ParseInt(split[3]));
            }

            bankInfo.Filename = split.Length > 4 ? split[4] : null;
        }
예제 #5
0
        private void readCustomSampleBanks(string str, SampleBankInfo bankInfo)
        {
            if (string.IsNullOrEmpty(str))
            {
                return;
            }

            string[] split = str.Split(':');

            var bank    = (LegacyDecoder.LegacySampleBank)Convert.ToInt32(split[0]);
            var addbank = (LegacyDecoder.LegacySampleBank)Convert.ToInt32(split[1]);

            // Let's not implement this for now, because this doesn't fit nicely into the bank structure
            //string sampleFile = split2.Length > 4 ? split2[4] : string.Empty;

            string stringBank = bank.ToString().ToLower();

            if (stringBank == @"none")
            {
                stringBank = null;
            }
            string stringAddBank = addbank.ToString().ToLower();

            if (stringAddBank == @"none")
            {
                stringAddBank = null;
            }

            bankInfo.Normal = stringBank;
            bankInfo.Add    = stringAddBank;

            if (split.Length > 3)
            {
                bankInfo.Volume = int.Parse(split[3]);
            }
        }
예제 #6
0
        public override HitObject Parse(string text)
        {
            try
            {
                string[] split = text.Split(',');

                Vector2 pos = new Vector2((int)Convert.ToSingle(split[0], CultureInfo.InvariantCulture), (int)Convert.ToSingle(split[1], CultureInfo.InvariantCulture));

                ConvertHitObjectType type = (ConvertHitObjectType)int.Parse(split[3]);

                int comboOffset = (int)(type & ConvertHitObjectType.ComboOffset) >> 4;
                type &= ~ConvertHitObjectType.ComboOffset;

                bool combo = type.HasFlag(ConvertHitObjectType.NewCombo);
                type &= ~ConvertHitObjectType.NewCombo;

                var soundType = (LegacySoundType)int.Parse(split[4]);
                var bankInfo  = new SampleBankInfo();

                HitObject result = null;

                if (type.HasFlag(ConvertHitObjectType.Circle))
                {
                    result = CreateHit(pos, combo, comboOffset);

                    if (split.Length > 5)
                    {
                        readCustomSampleBanks(split[5], bankInfo);
                    }
                }
                else if (type.HasFlag(ConvertHitObjectType.Slider))
                {
                    PathType pathType = PathType.Catmull;
                    double   length   = 0;

                    string[] pointSplit = split[5].Split('|');

                    int pointCount = 1;
                    foreach (var t in pointSplit)
                    {
                        if (t.Length > 1)
                        {
                            pointCount++;
                        }
                    }

                    var points = new Vector2[pointCount];

                    int pointIndex = 1;
                    foreach (string t in pointSplit)
                    {
                        if (t.Length == 1)
                        {
                            switch (t)
                            {
                            case @"C":
                                pathType = PathType.Catmull;
                                break;

                            case @"B":
                                pathType = PathType.Bezier;
                                break;

                            case @"L":
                                pathType = PathType.Linear;
                                break;

                            case @"P":
                                pathType = PathType.PerfectCurve;
                                break;
                            }

                            continue;
                        }

                        string[] temp = t.Split(':');
                        points[pointIndex++] = new Vector2((int)Convert.ToDouble(temp[0], CultureInfo.InvariantCulture), (int)Convert.ToDouble(temp[1], CultureInfo.InvariantCulture)) - pos;
                    }

                    // osu-stable special-cased colinear perfect curves to a CurveType.Linear
                    bool isLinear(Vector2[] p) => Precision.AlmostEquals(0, (p[1].Y - p[0].Y) * (p[2].X - p[0].X) - (p[1].X - p[0].X) * (p[2].Y - p[0].Y));

                    if (points.Length == 3 && pathType == PathType.PerfectCurve && isLinear(points))
                    {
                        pathType = PathType.Linear;
                    }

                    int repeatCount = Convert.ToInt32(split[6], CultureInfo.InvariantCulture);

                    if (repeatCount > 9000)
                    {
                        throw new ArgumentOutOfRangeException(nameof(repeatCount), @"Repeat count is way too high");
                    }

                    // osu-stable treated the first span of the slider as a repeat, but no repeats are happening
                    repeatCount = Math.Max(0, repeatCount - 1);

                    if (split.Length > 7)
                    {
                        length = Convert.ToDouble(split[7], CultureInfo.InvariantCulture);
                    }

                    if (split.Length > 10)
                    {
                        readCustomSampleBanks(split[10], bankInfo);
                    }

                    // One node for each repeat + the start and end nodes
                    int nodes = repeatCount + 2;

                    // Populate node sample bank infos with the default hit object sample bank
                    var nodeBankInfos = new List <SampleBankInfo>();
                    for (int i = 0; i < nodes; i++)
                    {
                        nodeBankInfos.Add(bankInfo.Clone());
                    }

                    // Read any per-node sample banks
                    if (split.Length > 9 && split[9].Length > 0)
                    {
                        string[] sets = split[9].Split('|');
                        for (int i = 0; i < nodes; i++)
                        {
                            if (i >= sets.Length)
                            {
                                break;
                            }

                            SampleBankInfo info = nodeBankInfos[i];
                            readCustomSampleBanks(sets[i], info);
                        }
                    }

                    // Populate node sound types with the default hit object sound type
                    var nodeSoundTypes = new List <LegacySoundType>();
                    for (int i = 0; i < nodes; i++)
                    {
                        nodeSoundTypes.Add(soundType);
                    }

                    // Read any per-node sound types
                    if (split.Length > 8 && split[8].Length > 0)
                    {
                        string[] adds = split[8].Split('|');
                        for (int i = 0; i < nodes; i++)
                        {
                            if (i >= adds.Length)
                            {
                                break;
                            }

                            int sound;
                            int.TryParse(adds[i], out sound);
                            nodeSoundTypes[i] = (LegacySoundType)sound;
                        }
                    }

                    // Generate the final per-node samples
                    var nodeSamples = new List <List <SampleInfo> >(nodes);
                    for (int i = 0; i < nodes; i++)
                    {
                        nodeSamples.Add(convertSoundType(nodeSoundTypes[i], nodeBankInfos[i]));
                    }

                    result = CreateSlider(pos, combo, comboOffset, points, length, pathType, repeatCount, nodeSamples);

                    // The samples are played when the slider ends, which is the last node
                    result.Samples = nodeSamples[nodeSamples.Count - 1];
                }
                else if (type.HasFlag(ConvertHitObjectType.Spinner))
                {
                    result = CreateSpinner(new Vector2(512, 384) / 2, combo, comboOffset, Convert.ToDouble(split[5], CultureInfo.InvariantCulture) + Offset);

                    if (split.Length > 6)
                    {
                        readCustomSampleBanks(split[6], bankInfo);
                    }
                }
                else if (type.HasFlag(ConvertHitObjectType.Hold))
                {
                    // Note: Hold is generated by BMS converts

                    double endTime = Convert.ToDouble(split[2], CultureInfo.InvariantCulture);

                    if (split.Length > 5 && !string.IsNullOrEmpty(split[5]))
                    {
                        string[] ss = split[5].Split(':');
                        endTime = Convert.ToDouble(ss[0], CultureInfo.InvariantCulture);
                        readCustomSampleBanks(string.Join(":", ss.Skip(1)), bankInfo);
                    }

                    result = CreateHold(pos, combo, comboOffset, endTime + Offset);
                }

                if (result == null)
                {
                    Logger.Log($"Unknown hit object type: {type}. Skipped.", level: LogLevel.Error);
                    return(null);
                }

                result.StartTime = Convert.ToDouble(split[2], CultureInfo.InvariantCulture) + Offset;

                if (result.Samples.Count == 0)
                {
                    result.Samples = convertSoundType(soundType, bankInfo);
                }

                FirstObject = false;

                return(result);
            }
            catch (FormatException)
            {
                throw new FormatException("One or more hit objects were malformed.");
            }
        }
예제 #7
0
        public override HitObject Parse(string text)
        {
            string[] split = text.Split(',');

            Vector2 pos = new Vector2((int)Parsing.ParseFloat(split[0], Parsing.MAX_COORDINATE_VALUE), (int)Parsing.ParseFloat(split[1], Parsing.MAX_COORDINATE_VALUE));

            double startTime = Parsing.ParseDouble(split[2]) + Offset;

            LegacyHitObjectType type = (LegacyHitObjectType)Parsing.ParseInt(split[3]);

            int comboOffset = (int)(type & LegacyHitObjectType.ComboOffset) >> 4;

            type &= ~LegacyHitObjectType.ComboOffset;

            bool combo = type.HasFlag(LegacyHitObjectType.NewCombo);

            type &= ~LegacyHitObjectType.NewCombo;

            var soundType = (LegacyHitSoundType)Parsing.ParseInt(split[4]);
            var bankInfo  = new SampleBankInfo();

            HitObject result = null;

            if (type.HasFlag(LegacyHitObjectType.Circle))
            {
                result = CreateHit(pos, combo, comboOffset);

                if (split.Length > 5)
                {
                    readCustomSampleBanks(split[5], bankInfo);
                }
            }
            else if (type.HasFlag(LegacyHitObjectType.Slider))
            {
                double?length = null;

                int repeatCount = Parsing.ParseInt(split[6]);

                if (repeatCount > 9000)
                {
                    throw new FormatException(@"Repeat count is way too high");
                }

                // osu-stable treated the first span of the slider as a repeat, but no repeats are happening
                repeatCount = Math.Max(0, repeatCount - 1);

                if (split.Length > 7)
                {
                    length = Math.Max(0, Parsing.ParseDouble(split[7], Parsing.MAX_COORDINATE_VALUE));
                    if (length == 0)
                    {
                        length = null;
                    }
                }

                if (split.Length > 10)
                {
                    readCustomSampleBanks(split[10], bankInfo);
                }

                // One node for each repeat + the start and end nodes
                int nodes = repeatCount + 2;

                // Populate node sample bank infos with the default hit object sample bank
                var nodeBankInfos = new List <SampleBankInfo>();
                for (int i = 0; i < nodes; i++)
                {
                    nodeBankInfos.Add(bankInfo.Clone());
                }

                // Read any per-node sample banks
                if (split.Length > 9 && split[9].Length > 0)
                {
                    string[] sets = split[9].Split('|');

                    for (int i = 0; i < nodes; i++)
                    {
                        if (i >= sets.Length)
                        {
                            break;
                        }

                        SampleBankInfo info = nodeBankInfos[i];
                        readCustomSampleBanks(sets[i], info);
                    }
                }

                // Populate node sound types with the default hit object sound type
                var nodeSoundTypes = new List <LegacyHitSoundType>();
                for (int i = 0; i < nodes; i++)
                {
                    nodeSoundTypes.Add(soundType);
                }

                // Read any per-node sound types
                if (split.Length > 8 && split[8].Length > 0)
                {
                    string[] adds = split[8].Split('|');

                    for (int i = 0; i < nodes; i++)
                    {
                        if (i >= adds.Length)
                        {
                            break;
                        }

                        int.TryParse(adds[i], out var sound);
                        nodeSoundTypes[i] = (LegacyHitSoundType)sound;
                    }
                }

                // Generate the final per-node samples
                var nodeSamples = new List <IList <HitSampleInfo> >(nodes);
                for (int i = 0; i < nodes; i++)
                {
                    nodeSamples.Add(convertSoundType(nodeSoundTypes[i], nodeBankInfos[i]));
                }

                result = CreateSlider(pos, combo, comboOffset, convertPathString(split[5], pos), length, repeatCount, nodeSamples);
            }
            else if (type.HasFlag(LegacyHitObjectType.Spinner))
            {
                double duration = Math.Max(0, Parsing.ParseDouble(split[5]) + Offset - startTime);

                result = CreateSpinner(new Vector2(512, 384) / 2, combo, comboOffset, duration);

                if (split.Length > 6)
                {
                    readCustomSampleBanks(split[6], bankInfo);
                }
            }
            else if (type.HasFlag(LegacyHitObjectType.Hold))
            {
                // Note: Hold is generated by BMS converts

                double endTime = Math.Max(startTime, Parsing.ParseDouble(split[2]));

                if (split.Length > 5 && !string.IsNullOrEmpty(split[5]))
                {
                    string[] ss = split[5].Split(':');
                    endTime = Math.Max(startTime, Parsing.ParseDouble(ss[0]));
                    readCustomSampleBanks(string.Join(':', ss.Skip(1)), bankInfo);
                }

                result = CreateHold(pos, combo, comboOffset, endTime + Offset - startTime);
            }

            if (result == null)
            {
                throw new InvalidDataException($"Unknown hit object type: {split[3]}");
            }

            result.StartTime = startTime;

            if (result.Samples.Count == 0)
            {
                result.Samples = convertSoundType(soundType, bankInfo);
            }

            FirstObject = false;

            return(result);
        }
예제 #8
0
        public override HitObject Parse(string text)
        {
            string[] split = text.Split(',');

            Vector2 pos = new Vector2((int)Parsing.ParseFloat(split[0], Parsing.MAX_COORDINATE_VALUE), (int)Parsing.ParseFloat(split[1], Parsing.MAX_COORDINATE_VALUE));

            double startTime = Parsing.ParseDouble(split[2]) + Offset;

            LegacyHitObjectType type = (LegacyHitObjectType)Parsing.ParseInt(split[3]);

            int comboOffset = (int)(type & LegacyHitObjectType.ComboOffset) >> 4;

            type &= ~LegacyHitObjectType.ComboOffset;

            bool combo = type.HasFlag(LegacyHitObjectType.NewCombo);

            type &= ~LegacyHitObjectType.NewCombo;

            var soundType = (LegacyHitSoundType)Parsing.ParseInt(split[4]);
            var bankInfo  = new SampleBankInfo();

            HitObject result = null;

            if (type.HasFlag(LegacyHitObjectType.Circle))
            {
                result = CreateHit(pos, combo, comboOffset);

                if (split.Length > 5)
                {
                    readCustomSampleBanks(split[5], bankInfo);
                }
            }
            else if (type.HasFlag(LegacyHitObjectType.Slider))
            {
                PathType pathType = PathType.Catmull;
                double?  length   = null;

                string[] pointSplit = split[5].Split('|');

                int pointCount = 1;

                foreach (var t in pointSplit)
                {
                    if (t.Length > 1)
                    {
                        pointCount++;
                    }
                }

                var points = new Vector2[pointCount];

                int pointIndex = 1;

                foreach (string t in pointSplit)
                {
                    if (t.Length == 1)
                    {
                        switch (t)
                        {
                        case @"C":
                            pathType = PathType.Catmull;
                            break;

                        case @"B":
                            pathType = PathType.Bezier;
                            break;

                        case @"L":
                            pathType = PathType.Linear;
                            break;

                        case @"P":
                            pathType = PathType.PerfectCurve;
                            break;
                        }

                        continue;
                    }

                    string[] temp = t.Split(':');
                    points[pointIndex++] = new Vector2((int)Parsing.ParseDouble(temp[0], Parsing.MAX_COORDINATE_VALUE), (int)Parsing.ParseDouble(temp[1], Parsing.MAX_COORDINATE_VALUE)) - pos;
                }

                int repeatCount = Parsing.ParseInt(split[6]);

                if (repeatCount > 9000)
                {
                    throw new FormatException(@"Repeat count is way too high");
                }

                // osu-stable treated the first span of the slider as a repeat, but no repeats are happening
                repeatCount = Math.Max(0, repeatCount - 1);

                if (split.Length > 7)
                {
                    length = Math.Max(0, Parsing.ParseDouble(split[7], Parsing.MAX_COORDINATE_VALUE));
                    if (length == 0)
                    {
                        length = null;
                    }
                }

                if (split.Length > 10)
                {
                    readCustomSampleBanks(split[10], bankInfo);
                }

                // One node for each repeat + the start and end nodes
                int nodes = repeatCount + 2;

                // Populate node sample bank infos with the default hit object sample bank
                var nodeBankInfos = new List <SampleBankInfo>();
                for (int i = 0; i < nodes; i++)
                {
                    nodeBankInfos.Add(bankInfo.Clone());
                }

                // Read any per-node sample banks
                if (split.Length > 9 && split[9].Length > 0)
                {
                    string[] sets = split[9].Split('|');

                    for (int i = 0; i < nodes; i++)
                    {
                        if (i >= sets.Length)
                        {
                            break;
                        }

                        SampleBankInfo info = nodeBankInfos[i];
                        readCustomSampleBanks(sets[i], info);
                    }
                }

                // Populate node sound types with the default hit object sound type
                var nodeSoundTypes = new List <LegacyHitSoundType>();
                for (int i = 0; i < nodes; i++)
                {
                    nodeSoundTypes.Add(soundType);
                }

                // Read any per-node sound types
                if (split.Length > 8 && split[8].Length > 0)
                {
                    string[] adds = split[8].Split('|');

                    for (int i = 0; i < nodes; i++)
                    {
                        if (i >= adds.Length)
                        {
                            break;
                        }

                        int.TryParse(adds[i], out var sound);
                        nodeSoundTypes[i] = (LegacyHitSoundType)sound;
                    }
                }

                // Generate the final per-node samples
                var nodeSamples = new List <IList <HitSampleInfo> >(nodes);
                for (int i = 0; i < nodes; i++)
                {
                    nodeSamples.Add(convertSoundType(nodeSoundTypes[i], nodeBankInfos[i]));
                }

                result = CreateSlider(pos, combo, comboOffset, convertControlPoints(points, pathType), length, repeatCount, nodeSamples);

                // The samples are played when the slider ends, which is the last node
                result.Samples = nodeSamples[^ 1];
예제 #9
0
        public override HitObject Parse(string text)
        {
            string[] split = text.Split(',');

            Vector2 pos = new Vector2((int)Parsing.ParseFloat(split[0], Parsing.MAX_COORDINATE_VALUE), (int)Parsing.ParseFloat(split[1], Parsing.MAX_COORDINATE_VALUE));

            double startTime = Parsing.ParseDouble(split[2]) + Offset;

            ConvertHitObjectType type = (ConvertHitObjectType)Parsing.ParseInt(split[3]);

            int comboOffset = (int)(type & ConvertHitObjectType.ComboOffset) >> 4;

            type &= ~ConvertHitObjectType.ComboOffset;

            bool combo = type.HasFlag(ConvertHitObjectType.NewCombo);

            type &= ~ConvertHitObjectType.NewCombo;

            var soundType = (LegacySoundType)Parsing.ParseInt(split[4]);
            var bankInfo  = new SampleBankInfo();

            HitObject result = null;

            if (type.HasFlag(ConvertHitObjectType.Circle))
            {
                result = CreateHit(pos, combo, comboOffset);

                if (split.Length > 5)
                {
                    readCustomSampleBanks(split[5], bankInfo);
                }
            }
            else if (type.HasFlag(ConvertHitObjectType.Slider))
            {
                PathType pathType = PathType.Catmull;
                double?  length   = null;

                string[] pointSplit = split[5].Split('|');

                int pointCount = 1;

                foreach (var t in pointSplit)
                {
                    if (t.Length > 1)
                    {
                        pointCount++;
                    }
                }

                var points = new Vector2[pointCount];

                int pointIndex = 1;

                foreach (string t in pointSplit)
                {
                    if (t.Length == 1)
                    {
                        switch (t)
                        {
                        case @"C":
                            pathType = PathType.Catmull;
                            break;

                        case @"B":
                            pathType = PathType.Bezier;
                            break;

                        case @"L":
                            pathType = PathType.Linear;
                            break;

                        case @"P":
                            pathType = PathType.PerfectCurve;
                            break;
                        }

                        continue;
                    }

                    string[] temp = t.Split(':');
                    points[pointIndex++] = new Vector2((int)Parsing.ParseDouble(temp[0], Parsing.MAX_COORDINATE_VALUE), (int)Parsing.ParseDouble(temp[1], Parsing.MAX_COORDINATE_VALUE)) - pos;
                }
예제 #10
0
        public override HitObject Parse(string text)
        {
            try
            {
                string[] split = text.Split(',');

                ConvertHitObjectType type = (ConvertHitObjectType)int.Parse(split[3]) & ~ConvertHitObjectType.ColourHax;
                bool combo = type.HasFlag(ConvertHitObjectType.NewCombo);
                type &= ~ConvertHitObjectType.NewCombo;

                var soundType = (LegacySoundType)int.Parse(split[4]);
                var bankInfo  = new SampleBankInfo();

                HitObject result = null;

                if ((type & ConvertHitObjectType.Circle) > 0)
                {
                    result = CreateHit(new Vector2(int.Parse(split[0]), int.Parse(split[1])), combo);

                    if (split.Length > 5)
                    {
                        readCustomSampleBanks(split[5], bankInfo);
                    }
                }
                else if ((type & ConvertHitObjectType.Slider) > 0)
                {
                    CurveType curveType = CurveType.Catmull;
                    double    length    = 0;
                    var       points    = new List <Vector2> {
                        new Vector2(int.Parse(split[0]), int.Parse(split[1]))
                    };

                    string[] pointsplit = split[5].Split('|');
                    foreach (string t in pointsplit)
                    {
                        if (t.Length == 1)
                        {
                            switch (t)
                            {
                            case @"C":
                                curveType = CurveType.Catmull;
                                break;

                            case @"B":
                                curveType = CurveType.Bezier;
                                break;

                            case @"L":
                                curveType = CurveType.Linear;
                                break;

                            case @"P":
                                curveType = CurveType.PerfectCurve;
                                break;
                            }
                            continue;
                        }

                        string[] temp = t.Split(':');
                        points.Add(new Vector2((int)Convert.ToDouble(temp[0], CultureInfo.InvariantCulture), (int)Convert.ToDouble(temp[1], CultureInfo.InvariantCulture)));
                    }

                    int repeatCount = Convert.ToInt32(split[6], CultureInfo.InvariantCulture);

                    if (repeatCount > 9000)
                    {
                        throw new ArgumentOutOfRangeException(nameof(repeatCount), @"Repeat count is way too high");
                    }

                    if (split.Length > 7)
                    {
                        length = Convert.ToDouble(split[7], CultureInfo.InvariantCulture);
                    }

                    if (split.Length > 10)
                    {
                        readCustomSampleBanks(split[10], bankInfo);
                    }

                    // One node for each repeat + the start and end nodes
                    // Note that the first length of the slider is considered a repeat, but there are no actual repeats happening
                    int nodes = Math.Max(0, repeatCount - 1) + 2;

                    // Populate node sample bank infos with the default hit object sample bank
                    var nodeBankInfos = new List <SampleBankInfo>();
                    for (int i = 0; i < nodes; i++)
                    {
                        nodeBankInfos.Add(bankInfo.Clone());
                    }

                    // Read any per-node sample banks
                    if (split.Length > 9 && split[9].Length > 0)
                    {
                        string[] sets = split[9].Split('|');
                        for (int i = 0; i < nodes; i++)
                        {
                            if (i >= sets.Length)
                            {
                                break;
                            }

                            SampleBankInfo info = nodeBankInfos[i];
                            readCustomSampleBanks(sets[i], info);
                        }
                    }

                    // Populate node sound types with the default hit object sound type
                    var nodeSoundTypes = new List <LegacySoundType>();
                    for (int i = 0; i < nodes; i++)
                    {
                        nodeSoundTypes.Add(soundType);
                    }

                    // Read any per-node sound types
                    if (split.Length > 8 && split[8].Length > 0)
                    {
                        string[] adds = split[8].Split('|');
                        for (int i = 0; i < nodes; i++)
                        {
                            if (i >= adds.Length)
                            {
                                break;
                            }

                            int sound;
                            int.TryParse(adds[i], out sound);
                            nodeSoundTypes[i] = (LegacySoundType)sound;
                        }
                    }

                    // Generate the final per-node samples
                    var nodeSamples = new List <SampleInfoList>(nodes);
                    for (int i = 0; i <= repeatCount; i++)
                    {
                        nodeSamples.Add(convertSoundType(nodeSoundTypes[i], nodeBankInfos[i]));
                    }

                    result = CreateSlider(new Vector2(int.Parse(split[0]), int.Parse(split[1])), combo, points, length, curveType, repeatCount, nodeSamples);
                }
                else if ((type & ConvertHitObjectType.Spinner) > 0)
                {
                    result = CreateSpinner(new Vector2(512, 384) / 2, Convert.ToDouble(split[5], CultureInfo.InvariantCulture));

                    if (split.Length > 6)
                    {
                        readCustomSampleBanks(split[6], bankInfo);
                    }
                }
                else if ((type & ConvertHitObjectType.Hold) > 0)
                {
                    // Note: Hold is generated by BMS converts

                    double endTime = Convert.ToDouble(split[2], CultureInfo.InvariantCulture);

                    if (split.Length > 5 && !string.IsNullOrEmpty(split[5]))
                    {
                        string[] ss = split[5].Split(':');
                        endTime = Convert.ToDouble(ss[0], CultureInfo.InvariantCulture);
                        readCustomSampleBanks(string.Join(":", ss.Skip(1)), bankInfo);
                    }

                    result = CreateHold(new Vector2(int.Parse(split[0]), int.Parse(split[1])), combo, endTime);
                }

                if (result == null)
                {
                    throw new InvalidOperationException($@"Unknown hit object type {type}.");
                }

                result.StartTime = Convert.ToDouble(split[2], CultureInfo.InvariantCulture);
                result.Samples   = convertSoundType(soundType, bankInfo);

                return(result);
            }
            catch (FormatException)
            {
                throw new FormatException("One or more hit objects were malformed.");
            }
        }