public void Special_SimplePairWithoutParentheses()
        {
            var s       = @"x:[1,1/4,3]";
            var special = Special.FromString(s);

            var std = new Special {
                Header = new NoteHeader {
                    Measure     = 1,
                    Nominator   = 1,
                    Denominator = 4,
                    Start       = 3,
                    End         = 3,
                    Speed       = 1
                }
            };

            Assert.AreEqual(std, special);
        }
        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);
                    }
                }
            }
        }