public BeatmapDetails(IBeatmapLevel level)
        {
            if (level == null)
            {
                throw new ArgumentNullException(nameof(level), "IBeatmapLevel parameter 'level' cannot be null");
            }
            else if (level.beatmapLevelData == null)
            {
                throw new ArgumentException("Provided IBeatmapLevel object cannot have the 'beatmapLevelData' property be null", nameof(level));
            }

            // remove the directory part of a custom level ID
            LevelID = BeatmapDetailsLoader.GetSimplifiedLevelID(level);

            SongName       = level.songName;
            BeatsPerMinute = level.beatsPerMinute;

            if (level.beatmapLevelData.audioClip != null)
            {
                SongDuration = level.beatmapLevelData.audioClip.length;
            }
            else
            {
                SongDuration = level.songDuration;
                if (level is CustomBeatmapLevel)
                {
                    Logger.log.Debug("Using stored song duration for custom song (might not work)");
                }
            }

            var levelDifficultyBeatmapSets = level.beatmapLevelData.difficultyBeatmapSets;

            DifficultyBeatmapSets = new SimplifiedDifficultyBeatmapSet[levelDifficultyBeatmapSets.Count()];
            for (int i = 0; i < levelDifficultyBeatmapSets.Count(); ++i)
            {
                SimplifiedDifficultyBeatmapSet newSet = new SimplifiedDifficultyBeatmapSet();
                DifficultyBeatmapSets[i] = newSet;

                newSet.CharacteristicName = levelDifficultyBeatmapSets[i].beatmapCharacteristic.serializedName;

                var levelDifficultyBeatmaps = levelDifficultyBeatmapSets[i].difficultyBeatmaps;
                newSet.DifficultyBeatmaps = new SimplifiedDifficultyBeatmap[levelDifficultyBeatmaps.Length];
                for (int j = 0; j < levelDifficultyBeatmaps.Length; ++j)
                {
                    SimplifiedDifficultyBeatmap newDiff = new SimplifiedDifficultyBeatmap();
                    newSet.DifficultyBeatmaps[j] = newDiff;

                    newDiff.Difficulty               = levelDifficultyBeatmaps[j].difficulty;
                    newDiff.NoteJumpMovementSpeed    = levelDifficultyBeatmaps[j].noteJumpMovementSpeed;
                    newDiff.NotesCount               = levelDifficultyBeatmaps[j].beatmapData.notesCount;
                    newDiff.BombsCount               = levelDifficultyBeatmaps[j].beatmapData.bombsCount;
                    newDiff.ObstaclesCount           = levelDifficultyBeatmaps[j].beatmapData.obstaclesCount;
                    newDiff.SpawnRotationEventsCount = levelDifficultyBeatmaps[j].beatmapData.spawnRotationEventsCount;
                }
            }
        }
        public static IEnumerator <BeatmapDetails> CreateBeatmapDetailsFromFilesCoroutine(CustomPreviewBeatmapLevel customLevel)
        {
            StandardLevelInfoSaveData infoData       = customLevel.standardLevelInfoSaveData;
            BeatmapDetails            beatmapDetails = new BeatmapDetails();

            beatmapDetails.LevelID        = BeatmapDetailsLoader.GetSimplifiedLevelID(customLevel);
            beatmapDetails.SongName       = customLevel.songName;
            beatmapDetails.BeatsPerMinute = infoData.beatsPerMinute;

            // load difficulties
            beatmapDetails.DifficultyBeatmapSets = new SimplifiedDifficultyBeatmapSet[infoData.difficultyBeatmapSets.Length];
            for (int i = 0; i < infoData.difficultyBeatmapSets.Length; ++i)
            {
                var currentSimplifiedSet = new SimplifiedDifficultyBeatmapSet();
                beatmapDetails.DifficultyBeatmapSets[i] = currentSimplifiedSet;
                var currentSet = infoData.difficultyBeatmapSets[i];

                currentSimplifiedSet.CharacteristicName = currentSet.beatmapCharacteristicName;
                currentSimplifiedSet.DifficultyBeatmaps = new SimplifiedDifficultyBeatmap[currentSet.difficultyBeatmaps.Length];

                for (int j = 0; j < currentSet.difficultyBeatmaps.Length; ++j)
                {
                    var currentSimplifiedDiff = new SimplifiedDifficultyBeatmap();
                    currentSimplifiedSet.DifficultyBeatmaps[j] = currentSimplifiedDiff;
                    var currentDiff = currentSet.difficultyBeatmaps[j];

                    currentDiff.difficulty.BeatmapDifficultyFromSerializedName(out currentSimplifiedDiff.Difficulty);
                    currentSimplifiedDiff.NoteJumpMovementSpeed = currentDiff.noteJumpMovementSpeed;

                    string diffFilePath             = Path.Combine(customLevel.customLevelPath, currentDiff.beatmapFilename);
                    string fileContents             = null;
                    IEnumerator <string> textLoader = UnityMediaLoader.LoadTextCoroutine(diffFilePath);
                    while (textLoader.MoveNext())
                    {
                        fileContents = textLoader.Current;

                        if (fileContents == null)
                        {
                            yield return(null);
                        }
                    }

                    if (string.IsNullOrEmpty(fileContents))
                    {
                        yield break;
                    }

                    BeatmapSaveData beatmapSaveData = null;
                    try
                    {
                        beatmapSaveData = BeatmapSaveData.DeserializeFromJSONString(fileContents);
                    }
                    catch (Exception e)
                    {
                        Logger.log.Warn($"Exception occurred while trying to deserialize difficulty beatmap to BeatmapSaveData for '{customLevel.songName}'");
                        Logger.log.Debug(e);

                        yield break;
                    }

                    // missing difficulty files
                    if (beatmapSaveData == null)
                    {
                        yield break;
                    }

                    // count notes and bombs
                    currentSimplifiedDiff.NotesCount = 0;
                    currentSimplifiedDiff.BombsCount = 0;
                    foreach (var note in beatmapSaveData.notes)
                    {
                        if (note.type.IsBasicNote())
                        {
                            ++currentSimplifiedDiff.NotesCount;
                        }
                        else if (note.type == NoteType.Bomb)
                        {
                            ++currentSimplifiedDiff.BombsCount;
                        }
                    }

                    // count rotation events
                    currentSimplifiedDiff.SpawnRotationEventsCount = 0;
                    foreach (var mapEvent in beatmapSaveData.events)
                    {
                        if (mapEvent.type.IsRotationEvent())
                        {
                            ++currentSimplifiedDiff.SpawnRotationEventsCount;
                        }
                    }

                    currentSimplifiedDiff.ObstaclesCount = beatmapSaveData.obstacles.Count;
                }
            }

            // load audio
            string    audioFilePath             = Path.Combine(customLevel.customLevelPath, infoData.songFilename);
            AudioClip audioClip                 = null;
            IEnumerator <AudioClip> audioLoader = UnityMediaLoader.LoadAudioClipCoroutine(audioFilePath);

            while (audioLoader.MoveNext())
            {
                audioClip = audioLoader.Current;

                if (audioClip == null)
                {
                    yield return(null);
                }
            }

            if (audioClip == null)
            {
                yield break;
            }

            beatmapDetails.SongDuration = audioClip.length;

            yield return(beatmapDetails);
        }
        /// <summary>
        /// Loads files associated with a custom beatmap and creates a BeatmapDetails object with the information contained in the files.
        /// </summary>
        /// <param name="customLevel">A custom level to create the BeatmapDetails object for.</param>
        /// <returns>BeatmapDetails object on success, otherwise null.</returns>
        public static BeatmapDetails CreateBeatmapDetailsFromFiles(CustomPreviewBeatmapLevel customLevel)
        {
            StandardLevelInfoSaveData infoData       = customLevel.standardLevelInfoSaveData;
            BeatmapDetails            beatmapDetails = new BeatmapDetails();

            beatmapDetails.LevelID        = BeatmapDetailsLoader.GetSimplifiedLevelID(customLevel);
            beatmapDetails.SongName       = customLevel.songName;
            beatmapDetails.BeatsPerMinute = infoData.beatsPerMinute;

            // load difficulties for note info
            beatmapDetails.DifficultyBeatmapSets = new SimplifiedDifficultyBeatmapSet[infoData.difficultyBeatmapSets.Length];
            for (int i = 0; i < infoData.difficultyBeatmapSets.Length; ++i)
            {
                var currentSimplifiedSet = new SimplifiedDifficultyBeatmapSet();
                beatmapDetails.DifficultyBeatmapSets[i] = currentSimplifiedSet;
                var currentSet = infoData.difficultyBeatmapSets[i];

                currentSimplifiedSet.CharacteristicName = currentSet.beatmapCharacteristicName;
                currentSimplifiedSet.DifficultyBeatmaps = new SimplifiedDifficultyBeatmap[currentSet.difficultyBeatmaps.Length];

                for (int j = 0; j < currentSet.difficultyBeatmaps.Length; ++j)
                {
                    var currentSimplifiedDiff = new SimplifiedDifficultyBeatmap();
                    currentSimplifiedSet.DifficultyBeatmaps[j] = currentSimplifiedDiff;
                    var currentDiff = currentSet.difficultyBeatmaps[j];

                    currentDiff.difficulty.BeatmapDifficultyFromSerializedName(out currentSimplifiedDiff.Difficulty);
                    currentSimplifiedDiff.NoteJumpMovementSpeed = currentDiff.noteJumpMovementSpeed;

                    string diffFilePath = Path.Combine(customLevel.customLevelPath, currentDiff.beatmapFilename);
                    if (!File.Exists(diffFilePath))
                    {
                        return(null);
                    }

                    BeatmapSaveData beatmapSaveData = null;
                    try
                    {
                        beatmapSaveData = BeatmapSaveData.DeserializeFromJSONString(File.ReadAllText(diffFilePath));
                    }
                    catch (Exception e)
                    {
                        Logger.log.Debug("Unable to create BeatmapDetails object from files (unexpected exception occurred trying to load BeatmapSaveData from file)");
                        Logger.log.Debug(e);
                        return(null);
                    }

                    if (beatmapSaveData == null)
                    {
                        Logger.log.Debug("Unable to create BeatmapDetails object from files (could not load BeatmapSaveData from file)");
                        return(null);
                    }

                    // count notes and bombs
                    currentSimplifiedDiff.NotesCount = 0;
                    currentSimplifiedDiff.BombsCount = 0;
                    foreach (var note in beatmapSaveData.notes)
                    {
                        if (note.type.IsBasicNote())
                        {
                            ++currentSimplifiedDiff.NotesCount;
                        }
                        else if (note.type == NoteType.Bomb)
                        {
                            ++currentSimplifiedDiff.BombsCount;
                        }
                    }

                    // count rotation events
                    currentSimplifiedDiff.SpawnRotationEventsCount = 0;
                    foreach (var mapEvent in beatmapSaveData.events)
                    {
                        if (mapEvent.type.IsRotationEvent())
                        {
                            ++currentSimplifiedDiff.SpawnRotationEventsCount;
                        }
                    }

                    currentSimplifiedDiff.ObstaclesCount = beatmapSaveData.obstacles.Count;
                }
            }

            // load audio for map length
            string    audioFilePath = Path.Combine(customLevel.customLevelPath, infoData.songFilename);
            AudioClip audioClip     = UnityMediaLoader.LoadAudioClip(audioFilePath);

            if (audioClip == null)
            {
                return(null);
            }

            beatmapDetails.SongDuration = audioClip.length;

            return(beatmapDetails);
        }