예제 #1
0
        public RuntimeScore ReadCompiledScore(Stream stream, string fileName, ReadSourceOptions sourceOptions, ScoreCompileOptions compileOptions)
        {
            var score = ReadSourceScore(stream, fileName, sourceOptions);

            using (var compiler = new SldprojCompiler()) {
                return(compiler.Compile(score, compileOptions));
            }
        }
        public RuntimeScore ReadCompiledScore(Stream stream, string fileName, ReadSourceOptions sourceOptions, ScoreCompileOptions compileOptions)
        {
            var          sourceScore = ReadSourceScore(stream, fileName, sourceOptions);
            RuntimeScore runtimeScore;

            using (var compiler = new SimpleScoreCompiler()) {
                runtimeScore = compiler.Compile(sourceScore, compileOptions);
            }
            return(runtimeScore);
        }
예제 #3
0
        public SourceScore ReadSourceScore(Stream stream, string fileName, ReadSourceOptions sourceOptions)
        {
            var extension = Path.GetExtension(fileName);

            if (extension == null)
            {
                throw new ArgumentException();
            }

            var         lz4Extension = Unity3DScoreFormat.Unity3DReadExtensions[1];
            var         isCompressed = extension.ToLowerInvariant().EndsWith(lz4Extension);
            ScoreObject scoreObject  = null;

            using (var bundle = new BundleFile(stream, fileName, isCompressed)) {
                foreach (var asset in bundle.AssetFiles)
                {
                    foreach (var preloadData in asset.PreloadDataList)
                    {
                        if (preloadData.KnownType != KnownClassID.MonoBehaviour)
                        {
                            continue;
                        }

                        var behaviour = preloadData.LoadAsMonoBehaviour(true);
                        if (!behaviour.Name.Contains("fumen"))
                        {
                            continue;
                        }

                        behaviour = preloadData.LoadAsMonoBehaviour(false);
                        var serializer = new MonoBehaviourSerializer();
                        scoreObject = serializer.Deserialize <ScoreObject>(behaviour);
                        break;
                    }
                }
            }

            if (scoreObject == null)
            {
                throw new FormatException();
            }

            return(ToSourceScore(scoreObject, sourceOptions));
        }
예제 #4
0
        private static void Main(string[] args)
        {
            var format        = new SimpleScoreFormat();
            var sourceOptions = new ReadSourceOptions {
                ScoreIndex = 0
            };

            SourceScore score;

            using (var fileStream = File.Open("simple.ss", FileMode.Open, FileAccess.Read, FileShare.Read)) {
                using (var reader = format.CreateReader()) {
                    score = reader.ReadSourceScore(fileStream, fileStream.Name, sourceOptions);
                }
            }

            Console.WriteLine(score.Notes.Length);
#if DEBUG
            Console.ReadKey();
#endif
        }
예제 #5
0
        private static SourceScore ToSourceScore(ScoreObject scoreObject, ReadSourceOptions options)
        {
            var score = new SourceScore();

            var scoreIndex = options.ScoreIndex;
            var difficulty = (Difficulty)scoreIndex;
            var trackType  = ScoreHelper.MapDifficultyToTrackType(difficulty);
            var tracks     = ScoreHelper.GetTrackIndicesFromTrackType(trackType);

            score.Notes = scoreObject.NoteEvents
                          .Where(nd => Array.IndexOf(tracks, nd.Track) >= 0)
                          .Select(n => ToNote(n, tracks))
                          .Where(n => n != null).ToArray();
            score.Conductors  = scoreObject.ConductorEvents.Select(ToConductor).ToArray();
            score.MusicOffset = scoreObject.BgmOffset;

            score.ScoreIndex = scoreIndex;
            score.TrackCount = tracks.Length;

            return(score);
        }
예제 #6
0
        public SourceScore ReadSourceScore(Stream stream, string fileName, ReadSourceOptions sourceOptions)
        {
            var projectVersion = ProjectReader.CheckFormatVersion(fileName);

            ProjectReader projectReader;

            switch (projectVersion)
            {
            case ProjectVersion.V0_2:
                projectReader = new SldprojV2Reader();
                break;

            case ProjectVersion.V0_3:
            case ProjectVersion.V0_3_1:
                projectReader = new SldprojV3Reader();
                break;

            case ProjectVersion.V0_4:
                projectReader = new SldprojV4Reader();
                break;

            default:
                throw new FormatException("Unsupported sldproj format version.");
            }

            var project    = projectReader.ReadProject(fileName, null);
            var difficulty = (Difficulty)(sourceOptions.ScoreIndex + 1);

            var score = project.GetScore(difficulty);

            if (score == null)
            {
                throw new FormatException($"Invalid project or invalid score index (\"{sourceOptions.ScoreIndex}\").");
            }

            return(ToSourceScore(project, score, sourceOptions));
        }
        public SourceScore ReadSourceScore(Stream stream, string fileName, ReadSourceOptions sourceOptions)
        {
            var lines = ReadLines(stream);

            var c = 0;

            var offsetTimeSpan = TimeSpan.Parse(lines[c]);

            ++c;

            var leadTimeSpan = TimeSpan.Parse(lines[c]);

            ++c;

            var trackCount = Convert.ToInt32(lines[c]);

            ++c;

            var beatsPerMeasure = Convert.ToInt32(lines[c]);

            ++c;

            // splitter
            ++c;

            var notes = new List <SourceNote>();

            while (!lines[c].StartsWith("---"))
            {
                var line = lines[c];

                SourceNote[] sourceNotes;
                if (TapPattern.IsMatch(line))
                {
                    var tap = Tap.FromString(line);

                    var sourceNote = new SourceNote {
                        Type = NoteType.Tap
                    };
                    FillHeader(sourceNote, tap.Header);
                    sourceNote.Size = StrToSize(tap.Body.Size);

                    sourceNotes = new[] { sourceNote };
                }
                else if (HoldPairPattern.IsMatch(line))
                {
                    var holds = Hold.CreateHolds(line);

                    var sourceNote = new SourceNote {
                        Type = NoteType.Hold
                    };
                    FillHeader(sourceNote, holds[0].Header);
                    sourceNote.Size = StrToSize(holds[0].Body.Size);

                    var holdEnd = new SourceNote {
                        Type = NoteType.Hold
                    };
                    FillHeader(holdEnd, holds[1].Header);
                    holdEnd.Size           = sourceNote.Size;
                    holdEnd.FlickDirection = StrToDirection(holds[1].Body.Direction);

                    sourceNote.FollowingNotes = new[] { holdEnd };

                    sourceNotes = new[] { sourceNote };
                }
                else if (FlickPattern.IsMatch(line))
                {
                    var flick = Flick.FromString(line);

                    var sourceNote = new SourceNote {
                        Type = NoteType.Flick
                    };
                    FillHeader(sourceNote, flick.Header);
                    sourceNote.Size           = StrToSize(flick.Body.Size);
                    sourceNote.FlickDirection = StrToDirection(flick.Body.Direction);

                    sourceNotes = new[] { sourceNote };
                }
                else if (SlideSeriesPattern.IsMatch(line))
                {
                    var slides = Slide.CreateSlides(line);

                    var sourceNote = new SourceNote {
                        Type = NoteType.Slide
                    };
                    FillHeader(sourceNote, slides[0].Header);

                    var following = new List <SourceNote>();
                    for (var i = 1; i < slides.Count; ++i)
                    {
                        var nodeInSeries = new SourceNote {
                            Type = NoteType.Slide
                        };
                        FillHeader(nodeInSeries, slides[i].Header);

                        if (i == slides.Count - 1)
                        {
                            nodeInSeries.FlickDirection = StrToDirection(slides[i].Body.Direction);
                        }

                        following.Add(nodeInSeries);
                    }

                    sourceNote.FollowingNotes = following.ToArray();

                    sourceNotes = new[] { sourceNote };
                }
                else if (SpecialPattern.IsMatch(line))
                {
                    var special = Special.FromString(line);

                    var sourceNote = new SourceNote {
                        Type = NoteType.Special
                    };
                    FillHeader(sourceNote, special.Header);

                    sourceNotes = new[] { sourceNote };
                }
                else
                {
                    throw new FormatException("Error in simple format.");
                }

                notes.AddRange(sourceNotes);

                // next line
                ++c;
            }

            // Sort the added notes.
            notes.Sort((n1, n2) => n1.Ticks.CompareTo(n2.Ticks));

            // splitter
            ++c;

            var conductors = new List <Conductor>();

            for (; c < lines.Count; ++c)
            {
                var ss           = lines[c].Split(':');
                var measureIndex = Convert.ToInt32(ss[0]);
                var bpm          = Convert.ToDouble(ss[1]);
                var conductor    = new Conductor {
                    Measure              = measureIndex - 1,
                    Tempo                = bpm,
                    Ticks                = (measureIndex - 1) * beatsPerMeasure * NoteBase.TicksPerBeat,
                    SignatureNumerator   = beatsPerMeasure,
                    SignatureDenominator = beatsPerMeasure
                };
                conductors.Add(conductor);
            }

            conductors.Sort((n1, n2) => n1.Ticks.CompareTo(n2.Ticks));

            var score = new SourceScore();

            score.Conductors  = conductors.ToArray();
            score.Notes       = notes.ToArray();
            score.TrackCount  = trackCount;
            score.MusicOffset = offsetTimeSpan.TotalSeconds;
            return(score);

            void FillHeader(SourceNote note, NoteHeader header)
            {
                var fraction = (float)(header.Nominator - 1) / header.Denominator;

                note.Beat       = (int)(beatsPerMeasure * fraction);
                note.StartX     = header.Start - 1;
                note.EndX       = header.End - 1;
                note.Speed      = header.Speed;
                note.LeadTime   = leadTimeSpan.TotalSeconds;
                note.Measure    = header.Measure - 1;
                note.Ticks      = 60 * (long)(beatsPerMeasure * ((header.Measure - 1) + fraction) * NoteBase.TicksPerBeat);
                note.TrackIndex = (int)note.StartX;

                if (note.TrackIndex < 0 || note.TrackIndex >= trackCount)
                {
                    Debug.Print("Warning: Invalid track index \"{0}\", changing into range [0, {1}].", note.TrackIndex, trackCount - 1);

                    if (note.TrackIndex < 0)
                    {
                        note.TrackIndex = 0;
                    }
                    else if (note.TrackIndex >= trackCount)
                    {
                        note.TrackIndex = trackCount - 1;
                    }
                }
            }

            NoteSize StrToSize(string str)
            {
                if (string.IsNullOrEmpty(str))
                {
                    return(NoteSize.Small);
                }
                else
                {
                    switch (str)
                    {
                    case "small":
                        return(NoteSize.Small);

                    case "large":
                        return(NoteSize.Large);

                    default:
                        throw new ArgumentOutOfRangeException(nameof(str), str, null);
                    }
                }
            }

            FlickDirection StrToDirection(string str)
            {
                if (string.IsNullOrEmpty(str))
                {
                    return(FlickDirection.None);
                }
                else
                {
                    switch (str)
                    {
                    case "left":
                        return(FlickDirection.Left);

                    case "right":
                        return(FlickDirection.Right);

                    case "up":
                        return(FlickDirection.Up);

                    default:
                        throw new ArgumentOutOfRangeException(nameof(str), str, null);
                    }
                }
            }
        }
예제 #8
0
        private static int Main([NotNull, ItemNotNull] string[] args)
        {
            if (args.Length == 0)
            {
                Console.Error.WriteLine(HelpText);
                return(0);
            }

            var inputFile = Path.GetFullPath(args[0]);

            string outputScoreFile;

            if (args.Length >= 2)
            {
                outputScoreFile = args[1];
            }
            else
            {
                var fi   = new FileInfo(inputFile);
                var name = fi.FullName;
                outputScoreFile = name.Substring(0, name.Length - fi.Extension.Length) + ".txt";
            }

            string outputScenarioFile;

            if (args.Length >= 3)
            {
                outputScenarioFile = args[2];
            }
            else
            {
                var fi   = new FileInfo(inputFile);
                var name = fi.FullName;
                outputScenarioFile = name.Substring(0, name.Length - fi.Extension.Length) + "_scenario.txt";
            }

            var format = new SimpleScoreFormat();

            SourceScore sourceScore;

            using (var fileStream = File.Open(inputFile, FileMode.Open, FileAccess.Read, FileShare.Read)) {
                using (var reader = format.CreateReader()) {
                    var sourceOptions = new ReadSourceOptions();

                    sourceScore = reader.ReadSourceScore(fileStream, fileStream.Name, sourceOptions);
                }
            }

            for (var i = 1; i < sourceScore.Conductors.Length; ++i)
            {
                ScorePreprocessor.FixNoteTickInfo(sourceScore.Conductors[i], sourceScore);
            }

            foreach (var sourceNote in sourceScore.Notes)
            {
                ScorePreprocessor.FixNoteTickInfo(sourceNote, sourceScore);
            }

            using (var fileStream = File.Open(outputScoreFile, FileMode.Create, FileAccess.Write, FileShare.Write)) {
                using (var writer = new StreamWriter(fileStream, Utf8WithoutBom)) {
                    WriteScore.Write(sourceScore, writer);
                }
            }

            using (var fileStream = File.Open(outputScenarioFile, FileMode.Create, FileAccess.Write, FileShare.Write)) {
                using (var writer = new StreamWriter(fileStream, Utf8WithoutBom)) {
                    WriteScenario.Write(sourceScore, writer);
                }
            }

            return(0);
        }
예제 #9
0
        private static SourceScore ToSourceScore([NotNull] Project project, [NotNull] Score score, [NotNull] ReadSourceOptions options)
        {
            var result = new SourceScore();

            var sourceNotes = new List <SourceNote>();
            var conductors  = new List <Conductor>();

            const int beatsPerMeasure = 4;

            var globalConductor = new Conductor();

            globalConductor.Tempo = (float)project.Settings.BeatPerMinute;
            globalConductor.SignatureNumerator   = project.Settings.Signature;
            globalConductor.SignatureDenominator = beatsPerMeasure;

            conductors.Add(globalConductor);

            var allNotes = score.GetAllNotes();

            foreach (var note in allNotes)
            {
                SourceNote sourceNote = null;
                Conductor  conductor  = null;

                switch (note.Basic.Type)
                {
                case NoteType.TapOrFlick:
                    sourceNote = new SourceNote();
                    FillTapOrFlick(note, sourceNote);
                    break;

                case NoteType.Hold:
                    if (!note.Helper.IsHoldStart)
                    {
                        continue;
                    }

                    sourceNote = new SourceNote();
                    FillHold(note, sourceNote);
                    break;

                case NoteType.Slide:
                    if (!note.Helper.IsSlideStart)
                    {
                        continue;
                    }

                    sourceNote = new SourceNote();
                    FillSlide(note, sourceNote);
                    break;

                case NoteType.VariantBpm: {
                    conductor = new Conductor();

                    FillCommonProperties(note, conductor);

                    Debug.Assert(note.Params != null, "note.Params != null");

                    conductor.Tempo = (float)note.Params.NewBpm;
                    conductor.SignatureNumerator   = project.Settings.Signature;
                    conductor.SignatureDenominator = beatsPerMeasure;
                }
                break;

                default:
                    throw new ArgumentOutOfRangeException();
                }

                if (sourceNote != null)
                {
                    sourceNotes.Add(sourceNote);
                }

                if (conductor != null)
                {
                    conductors.Add(conductor);
                }
            }

            result.MusicOffset = project.Settings.StartTimeOffset;
            result.TrackCount  = CgssTrackCount;
            result.ScoreIndex  = options.ScoreIndex;

            sourceNotes.Sort((n1, n2) => n1.Ticks.CompareTo(n2.Ticks));
            conductors.Sort((n1, n2) => n1.Ticks.CompareTo(n2.Ticks));

            result.Notes      = sourceNotes.ToArray();
            result.Conductors = conductors.ToArray();

            return(result);

            void FillCommonProperties(Note note, NoteBase sourceNote)
            {
                var bar = note.Basic.Bar;

                sourceNote.Measure = bar.Basic.Index;

                var fraction = (float)note.Basic.IndexInGrid / bar.GetGridPerSignature() / beatsPerMeasure;

                sourceNote.Beat = (int)fraction;

                //sourceNote.GroupID = 0;

                sourceNote.Ticks = 60 * (long)(beatsPerMeasure * (sourceNote.Measure + fraction) * NoteBase.TicksPerBeat);
            }

            void FillSourceNoteProperties(Note note, SourceNote sourceNote)
            {
                FillCommonProperties(note, sourceNote);

                sourceNote.Size  = NoteSize.Small;
                sourceNote.Speed = 1.0f;

                sourceNote.StartX     = (float)(note.Basic.StartPosition - 1);
                sourceNote.EndX       = (float)(note.Basic.FinishPosition - 1);
                sourceNote.TrackIndex = (int)sourceNote.StartX;

                if (sourceNote.TrackIndex < 0 || sourceNote.TrackIndex > (CgssTrackCount - 1))
                {
                    Debug.Print("Warning: Invalid track index \"{0}\", changing into range [0, {1}].", sourceNote.TrackIndex, CgssTrackCount - 1);

                    if (sourceNote.TrackIndex < 0)
                    {
                        sourceNote.TrackIndex = 0;
                    }
                    else if (sourceNote.TrackIndex > (CgssTrackCount - 1))
                    {
                        sourceNote.TrackIndex = (CgssTrackCount - 1);
                    }
                }

                sourceNote.LeadTime = (float)DefaultLeadTime.TotalSeconds;
            }

            void FillTapOrFlick(Note note, SourceNote sourceNote)
            {
                FillSourceNoteProperties(note, sourceNote);

                switch (note.Basic.FlickType)
                {
                case NoteFlickType.None:
                    sourceNote.Type           = MilliSim.Contributed.Scores.NoteType.Tap;
                    sourceNote.FlickDirection = FlickDirection.None;
                    break;

                case NoteFlickType.Left:
                    sourceNote.Type           = MilliSim.Contributed.Scores.NoteType.Flick;
                    sourceNote.FlickDirection = FlickDirection.Left;
                    break;

                case NoteFlickType.Right:
                    sourceNote.Type           = MilliSim.Contributed.Scores.NoteType.Flick;
                    sourceNote.FlickDirection = FlickDirection.Right;
                    break;

                default:
                    throw new ArgumentOutOfRangeException();
                }
            }

            void FillHold(Note note, SourceNote sourceNote)
            {
                FillSourceNoteProperties(note, sourceNote);

                sourceNote.Type = MilliSim.Contributed.Scores.NoteType.Hold;

                var holdEnd = new SourceNote();

                Debug.Assert(note.Editor.HoldPair != null, "note.Editor.HoldPair != null");

                FillSourceNoteProperties(note.Editor.HoldPair, holdEnd);

                holdEnd.Type = MilliSim.Contributed.Scores.NoteType.Hold;

                switch (note.Editor.HoldPair.Basic.FlickType)
                {
                case NoteFlickType.None:
                    holdEnd.FlickDirection = FlickDirection.None;
                    break;

                case NoteFlickType.Left:
                    holdEnd.FlickDirection = FlickDirection.Left;
                    break;

                case NoteFlickType.Right:
                    holdEnd.FlickDirection = FlickDirection.Right;
                    break;

                default:
                    throw new ArgumentOutOfRangeException();
                }

                sourceNote.FollowingNotes = new[] { holdEnd };
            }

            void FillSlide(Note note, SourceNote sourceNote)
            {
                FillSourceNoteProperties(note, sourceNote);

                sourceNote.Type = MilliSim.Contributed.Scores.NoteType.Slide;

                var following = new List <SourceNote>();

                var nextSlideNode = note.Editor.NextSlide;

                while (nextSlideNode != null)
                {
                    var node = new SourceNote();

                    FillSourceNoteProperties(nextSlideNode, node);

                    node.Type = MilliSim.Contributed.Scores.NoteType.Slide;

                    if (nextSlideNode.Helper.IsSlideEnd)
                    {
                        switch (nextSlideNode.Basic.FlickType)
                        {
                        case NoteFlickType.None:
                            node.FlickDirection = FlickDirection.None;
                            break;

                        case NoteFlickType.Left:
                            node.FlickDirection = FlickDirection.Left;
                            break;

                        case NoteFlickType.Right:
                            node.FlickDirection = FlickDirection.Right;
                            break;

                        default:
                            throw new ArgumentOutOfRangeException();
                        }
                    }

                    following.Add(node);

                    nextSlideNode = nextSlideNode.Editor.NextSlide;
                }

                sourceNote.FollowingNotes = following.ToArray();
            }
        }
예제 #10
0
        public void LoadScoreFile([NotNull] string scoreFilePath, int scoreIndex, float scoreOffset)
        {
            var theaterDays = Game.ToBaseGame();
            var debug       = theaterDays.FindSingleElement <DebugOverlay>();

            if (string.IsNullOrEmpty(scoreFilePath))
            {
                if (debug != null)
                {
                    debug.AddLine("ERROR: Score file is not specified.");
                }
                return;
            }

            if (!File.Exists(scoreFilePath))
            {
                if (debug != null)
                {
                    debug.AddLine($"ERROR: Score file <{scoreFilePath}> is missing.");
                }
                return;
            }

            var scoreFormats = theaterDays.PluginManager.GetPluginsOfType <IScoreFormat>();

            if (scoreFormats.Count == 0)
            {
                if (debug != null)
                {
                    debug.AddLine("ERROR: No available score reader.");
                }
                return;
            }

            var sourceOptions = new ReadSourceOptions {
                ScoreIndex = scoreIndex
            };
            var compileOptions = new ScoreCompileOptions {
                GlobalSpeed = 1,
                Offset      = scoreOffset
            };

            var          successful   = false;
            RuntimeScore runtimeScore = null;
            SourceScore  sourceScore  = null;

            foreach (var format in scoreFormats)
            {
                if (!format.SupportsReadingFileType(scoreFilePath))
                {
                    continue;
                }

                using (var reader = format.CreateReader()) {
                    Stream fileStream = null;

                    if (reader.IsStreamingSupported)
                    {
                        fileStream = File.Open(scoreFilePath, FileMode.Open, FileAccess.Read, FileShare.Read);
                    }

                    if (!successful)
                    {
                        if (format.CanReadAsSource)
                        {
                            try {
                                sourceScore = reader.ReadSourceScore(fileStream, scoreFilePath, sourceOptions);
                                if (!format.CanBeCompiled)
                                {
                                    throw new InvalidOperationException("This format must support compiling source score to runtime score.");
                                }
                                using (var compiler = format.CreateCompiler()) {
                                    runtimeScore = compiler.Compile(sourceScore, compileOptions);
                                }
                                successful = true;
                            } catch (Exception ex) {
                                if (debug != null)
                                {
                                    debug.AddLine($"An exception is thrown while trying to read the score using <{format.PluginDescription}>: {ex.Message}");
                                    debug.AddLine(ex.StackTrace);
                                }
                            }
                        }
                    }

                    if (!successful)
                    {
                        if (format.CanReadAsCompiled)
                        {
                            try {
                                runtimeScore = reader.ReadCompiledScore(fileStream, scoreFilePath, sourceOptions, compileOptions);
                                successful   = true;
                            } catch (Exception ex) {
                                if (debug != null)
                                {
                                    debug.AddLine($"An exception is thrown while trying to read the score using <{format.PluginDescription}>: {ex.Message}");
                                    debug.AddLine(ex.StackTrace);
                                }
                            }
                        }
                    }

                    if (successful)
                    {
                        break;
                    }

                    fileStream?.Dispose();
                }
            }

            if (!successful)
            {
                if (debug != null)
                {
                    debug.AddLine($"ERROR: No score reader can read score file <{scoreFilePath}>.");
                }
            }
            else
            {
                _sourceScore = sourceScore;
                RuntimeScore = runtimeScore;

                if (debug != null)
                {
                    debug.AddLine($"Loaded score file: {scoreFilePath}");
                }
            }

            var noteReactor = theaterDays.FindSingleElement <NoteReactor>();

            noteReactor?.RecalculateReactions();

            var tapPoints = theaterDays.FindSingleElement <TapPoints>();

            tapPoints?.RecalcLayout();
        }
예제 #11
0
        protected override void OnInitialize()
        {
            base.OnInitialize();

            var settings      = Program.Settings;
            var scoreFileName = settings.Game.ScoreFile;
            var debug         = Game.AsTheaterDays().FindSingleElement <DebugOverlay>();

            if (string.IsNullOrEmpty(scoreFileName))
            {
                if (debug != null)
                {
                    debug.AddLine("ERROR: Score file is not specified.");
                }
                return;
            }

            if (!File.Exists(scoreFileName))
            {
                if (debug != null)
                {
                    debug.AddLine($"ERROR: Score file <{scoreFileName}> is missing.");
                }
                return;
            }

            if (Program.PluginManager.ScoreFormats.Count == 0)
            {
                if (debug != null)
                {
                    debug.AddLine("ERROR: No available score reader.");
                }
                return;
            }

            var sourceOptions = new ReadSourceOptions {
                ScoreIndex = settings.Game.ScoreIndex
            };
            var compileOptions = new ScoreCompileOptions {
                GlobalSpeed = 1,
                Offset      = settings.Game.ScoreOffset
            };

            var          successful   = false;
            RuntimeScore runtimeScore = null;
            SourceScore  sourceScore  = null;

            foreach (var format in Program.PluginManager.ScoreFormats)
            {
                if (!format.SupportsReadingFileType(scoreFileName))
                {
                    continue;
                }

                using (var reader = format.CreateReader()) {
                    using (var fileStream = File.Open(scoreFileName, FileMode.Open, FileAccess.Read, FileShare.Read)) {
                        if (!successful)
                        {
                            if (format.CanReadAsSource)
                            {
                                try {
                                    sourceScore = reader.ReadSourceScore(fileStream, scoreFileName, sourceOptions);
                                    if (!format.CanBeCompiled)
                                    {
                                        throw new InvalidOperationException("This format must support compiling source score to runtime score.");
                                    }
                                    using (var compiler = format.CreateCompiler()) {
                                        runtimeScore = compiler.Compile(sourceScore, compileOptions);
                                    }
                                    successful = true;
                                } catch (Exception ex) {
                                    if (debug != null)
                                    {
                                        debug.AddLine($"An exception is thrown while trying to read the score using <{format.PluginDescription}>: {ex.Message}");
                                        debug.AddLine(ex.StackTrace);
                                    }
                                }
                            }
                        }

                        if (!successful)
                        {
                            if (format.CanReadAsCompiled)
                            {
                                try {
                                    runtimeScore = reader.ReadCompiledScore(fileStream, scoreFileName, sourceOptions, compileOptions);
                                    successful   = true;
                                } catch (Exception ex) {
                                    if (debug != null)
                                    {
                                        debug.AddLine($"An exception is thrown while trying to read the score using <{format.PluginDescription}>: {ex.Message}");
                                        debug.AddLine(ex.StackTrace);
                                    }
                                }
                            }
                        }

                        if (successful)
                        {
                            break;
                        }
                    }
                }
            }

            if (!successful)
            {
                if (debug != null)
                {
                    debug.AddLine($"ERROR: No score reader can read score file <{scoreFileName}>.");
                }
            }
            else
            {
                _score       = sourceScore;
                RuntimeScore = runtimeScore;
                if (debug != null)
                {
                    debug.AddLine($"Loaded score file: {scoreFileName}");
                }
            }
        }