/// <summary> /// Converts specified custom sample info into a list of sample infos that will be used in the game play. /// </summary> private List <SoundInfo> GetSamples(SoundType type, CustomSampleInfo customInfo) { // Include normal sound as default var samples = new List <SoundInfo>() { new LegacySampleInfo() { Sample = customInfo.NormalSample, Sound = SoundInfo.HitNormal, Volume = customInfo.Volume, VariantNumber = customInfo.Variant } }; if ((type & SoundType.Finish) != 0) { samples.Add(new LegacySampleInfo() { Sample = customInfo.AdditionalSample, Sound = SoundInfo.HitFinish, Volume = customInfo.Volume, VariantNumber = customInfo.Variant }); } if ((type & SoundType.Whistle) != 0) { samples.Add(new LegacySampleInfo() { Sample = customInfo.AdditionalSample, Sound = SoundInfo.HitWhistle, Volume = customInfo.Volume, VariantNumber = customInfo.Variant }); } if ((type & SoundType.Clap) != 0) { samples.Add(new LegacySampleInfo() { Sample = customInfo.AdditionalSample, Sound = SoundInfo.HitClap, Volume = customInfo.Volume, VariantNumber = customInfo.Variant }); } return(samples); }
/// <summary> /// Parses custom sample information to support per-object sound variations. /// </summary> private void ParseCustomSample(string text, CustomSampleInfo info) { if (string.IsNullOrEmpty(text)) { return; } string[] splits = text.Split(':'); var normalType = (OsuBeatmapDecoder.SampleType)ParseUtils.ParseInt(splits[0]); var additionalType = (OsuBeatmapDecoder.SampleType)ParseUtils.ParseInt(splits[1]); string normalTypeStr = normalType.ToString().ToLowerInvariant(); if (normalTypeStr == "none") { normalTypeStr = null; } string additionalTypeStr = additionalType.ToString().ToLowerInvariant(); if (additionalTypeStr == "none") { additionalTypeStr = null; } info.NormalSample = normalTypeStr; info.AdditionalSample = string.IsNullOrEmpty(additionalTypeStr) ? normalTypeStr : additionalTypeStr; if (splits.Length > 2) { info.Variant = ParseUtils.ParseInt(splits[2]); } if (splits.Length > 3) { info.Volume = ParseUtils.ParseFloat(splits[3]) / 100f; } info.FileName = splits.Length > 4 ? splits[4] : null; }
public BaseHitObject Parse(string text) { try { string[] splits = text.Split(','); Vector2 pos = new Vector2(ParseUtils.ParseFloat(splits[0]), ParseUtils.ParseFloat(splits[1])); float startTime = ParseUtils.ParseFloat(splits[2]) + offset; HitObjectType type = (HitObjectType)ParseUtils.ParseInt(splits[3]); int comboOffset = (int)(type & HitObjectType.ComboOffset) >> 4; type &= ~HitObjectType.ComboOffset; bool isNewCombo = (int)(type & HitObjectType.NewCombo) != 0; type &= ~HitObjectType.NewCombo; var soundType = (SoundType)ParseUtils.ParseInt(splits[4]); var customSample = new CustomSampleInfo(); // Now parse the actual hit objects. BaseHitObject result = null; // If this object is a hit circle if ((type & HitObjectType.Circle) != 0) { result = CreateCircle(pos, isNewCombo, comboOffset); if (splits.Length > 5) { ParseCustomSample(splits[5], customSample); } } else if ((type & HitObjectType.Slider) != 0) { PathType pathType = PathType.Catmull; float length = 0; string[] pointSplits = splits[5].Split('|'); // Find the number of valid slider node points. int pointCount = 1; foreach (var p in pointSplits) { if (p.Length > 1) { pointCount++; } } // Parse node points var nodePoints = new Vector2[pointCount]; nodePoints[0] = Vector2.zero; int pointIndex = 1; foreach (var p in pointSplits) { // Determine which path type was found. if (p.Length == 1) { switch (p) { 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; } // Parse point position string[] pointPos = p.Split(':'); nodePoints[pointIndex++] = new Vector2(ParseUtils.ParseFloat(pointPos[0]), ParseUtils.ParseFloat(pointPos[1])) - pos; } // Change perfect curve to linear if certain conditions meet. if (nodePoints.Length == 3 && pathType == PathType.PerfectCurve && IsLinearPerfectCurve(nodePoints)) { pathType = PathType.Linear; } // Parse slider repeat count int repeatCount = ParseUtils.ParseInt(splits[6]); if (repeatCount > 9000) { throw new Exception(); } // Osu file has +1 addition to the actual number of repeats. repeatCount = Math.Max(0, repeatCount - 1); if (splits.Length > 7) { length = Math.Max(0, ParseUtils.ParseFloat(splits[7])); } if (splits.Length > 10) { ParseCustomSample(splits[10], customSample); } // Number of repeats + start(1) + end(1) int nodeCount = repeatCount + 2; // Parse per-node sound samples var nodeCustomSamples = new List <CustomSampleInfo>(); for (int i = 0; i < nodeCount; i++) { nodeCustomSamples.Add(customSample.Clone()); } if (splits.Length > 9 && splits[9].Length > 0) { string[] sets = splits[9].Split('|'); for (int i = 0; i < nodeCount; i++) { if (i >= sets.Length) { break; } ParseCustomSample(sets[i], nodeCustomSamples[i]); } } // Set all nodes' sample types to default. var nodeSampleTypes = new List <SoundType>(); for (int i = 0; i < nodeCount; i++) { nodeSampleTypes.Add(soundType); } // Parse per-node sample types if (splits.Length > 8 && splits[8].Length > 0) { string[] nodeSampleSplits = splits[8].Split('|'); for (int i = 0; i < nodeCount; i++) { if (i > nodeSampleSplits.Length) { break; } nodeSampleTypes[i] = (SoundType)ParseUtils.ParseInt(nodeSampleSplits[i]); } } // Map sample types to custom sample infos. var nodeSamples = new List <List <SoundInfo> >(nodeCount); for (int i = 0; i < nodeCount; i++) { nodeSamples.Add(GetSamples(nodeSampleTypes[i], nodeCustomSamples[i])); } result = CreateSlider(pos, isNewCombo, comboOffset, nodePoints, length, pathType, repeatCount, nodeSamples); // Hit sound for the root slider should be played at the end. result.Samples = nodeSamples[nodeSamples.Count - 1]; } else if ((type & HitObjectType.Spinner) != 0) { float endTime = Math.Max(startTime, ParseUtils.ParseFloat(splits[5]) + offset); result = CreateSpinner(pos, isNewCombo, comboOffset, endTime); if (splits.Length > 6) { ParseCustomSample(splits[6], customSample); } } else if ((type & HitObjectType.Hold) != 0) { float endTime = Math.Max(startTime, ParseUtils.ParseFloat(splits[2] + offset)); // I can understand all others except this, because Hold type only exists for Mania mode. if (splits.Length > 5 && !string.IsNullOrEmpty(splits[5])) { string[] sampleSplits = splits[5].Split(':'); endTime = Math.Max(startTime, ParseUtils.ParseFloat(sampleSplits[0])); ParseCustomSample(string.Join(":", sampleSplits.Skip(1).ToArray()), customSample); } result = CreateHold(pos, isNewCombo, comboOffset, endTime + offset); } if (result == null) { Logger.LogVerbose("HitObjectParser.Parse - Unknown hit object for line: " + text); return(null); } result.StartTime = startTime; if (result.Samples.Count == 0) { result.Samples = GetSamples(soundType, customSample); } isFirstObject = false; return(result); } catch (Exception e) { Logger.LogError($"HitObjectParser.Parse - Failed to parse line: {text}, Error: {e.Message}"); } return(null); }