Ejemplo n.º 1
0
        /// <summary>
        /// 譜面をパースします。
        /// </summary>
        /// <param name="str">.tjaフォーマットな文字列。</param>
        public TJADotNet(string str)
        {
            // COURSE: で分割 ついでにコメントも消す
            var splitedCourses = Regex.Replace(str, @" *//.*", "", RegexOptions.Multiline).Split(new string[] { "COURSE:" }, StringSplitOptions.None);

            // 各要素にCOURSE: をくっつける ただし、[0]は共通ヘッダなので、COURSE:をつけない。
            for (int i = 1; i < splitedCourses.Length; i++)
            {
                // COURSE:を再び付与してsplitedCoursesに入れる
                splitedCourses[i] = "COURSE:" + splitedCourses[i];
            }

            // 共通ヘッダを取り出して coursesに投げる。
            var commonHeader = splitedCourses[0];
            var courses      = new string[splitedCourses.Length - 1];

            courses = splitedCourses.Skip(1).ToArray();

            // 共通ヘッダのパース
            Chart.CommonHeader = GetHeaderFromString(commonHeader);

            // 各コースのパース
            for (int i = 0; i < courses.Length; i++)
            {
                void setComposite(string course, bool first)
                {
                    var(header, text, remain, side) = SplitCourse(course);
                    var headerList = GetHeaderFromString(header);

                    if (first)
                    {
                        Chart.Courses.Add(new Course(headerList, text));
                    }
                    if (side == "")
                    {
                        // #START だった
                        Chart.Courses[i].Measure.Common = getMeasureThisPlayerSide(text);
                    }
                    else if (side == "P1")
                    {
                        // #START P1だった
                        Chart.Courses[i].Measure.Player1 = getMeasureThisPlayerSide(text);
                    }
                    else if (side == "P2")
                    {
                        // #START P2だった
                        Chart.Courses[i].Measure.Player2 = getMeasureThisPlayerSide(text);
                    }
                    if (!string.IsNullOrWhiteSpace(remain))
                    {
                        setComposite(remain, false);
                    }
                }

                List <string> getMeasureThisPlayerSide(string playerMeasure)
                {
                    var reader     = new StringReader(playerMeasure);
                    var nowMeasure = "";
                    var measures   = new List <string>();

                    while (reader.Peek() > -1)
                    {
                        var nowLine = reader.ReadLine();
                        if (nowLine.Trim().IndexOf(",") > -1)
                        {
                            // 行にカンマがある
                            if (nowLine.Trim().StartsWith("#"))
                            {
                                // カンマがあるけど、多分命令行なので処理を続行する
                                nowMeasure += nowLine + NewLine;
                                continue;
                            }
                            else
                            {
                                // 小節の終わり
                                // 0,0, という書き方もあるので、ちゃんと今の小節だけ抜き出してやる。
                                void addOneMeasure(string measureLine)
                                {
                                    // ,を除く譜面の抜き出し。
                                    var target = measureLine.Substring(0, measureLine.IndexOf(","));

                                    // 空だった場合、一小節としてカウントする
                                    if (target == "")
                                    {
                                        // つまり,で一小節
                                        // 0,と解釈させる
                                        target = "0";
                                    }
                                    nowMeasure += target;
                                    // それをListに追加
                                    measures.Add(nowMeasure);
                                    // クリア
                                    nowMeasure = "";
                                    // 残りに,が存在する(=その行にまだ小節がある)
                                    var remain = measureLine.Substring(measureLine.IndexOf(",") + 1);

                                    if (remain.Contains(","))
                                    {
                                        // 再帰処理
                                        addOneMeasure(remain);
                                    }
                                }

                                addOneMeasure(nowLine.Trim());
                                continue;
                            }
                        }
                        else
                        {
                            // もちろん続ける
                            nowMeasure += nowLine + NewLine;
                            continue;
                        }
                    }
                    reader.Dispose();
                    return(measures);
                }

                setComposite(courses[i], true);
            }
            foreach (var common in Chart.CommonHeader)
            {
                bool header(string name)
                {
                    return(name == common.Name.Trim());
                }

                string subtitler(string value, out SubTitleModes subtitleMode)
                {
                    if (value.StartsWith("--") || value.StartsWith("++"))
                    {
                        var trimedValue = value.Substring(2);
                        if (value.StartsWith("--"))
                        {
                            subtitleMode = SubTitleModes.Hide;
                        }
                        else
                        {
                            subtitleMode = SubTitleModes.Show;
                        }
                        return(trimedValue);
                    }
                    else
                    {
                        subtitleMode = SubTitleModes.Hide;
                        return(value);
                    }
                }

                if (header("TITLE"))
                {
                    Chart.Info.Title = common.Value;
                }
                else if (header("SUBTITLE"))
                {
                    Chart.Info.SubTitle     = subtitler(common.Value, out var mode);
                    Chart.Info.SubTitleMode = mode;
                }
                else if (header("BPM"))
                {
                    if (double.TryParse(common.Value, out var result))
                    {
                        Chart.Info.BPM = result;
                    }
                }
                else if (header("WAVE"))
                {
                    Chart.Info.Wave = common.Value;
                }
                else if (header("OFFSET"))
                {
                    if (double.TryParse(common.Value, out var result))
                    {
                        Chart.Info.Offset = result;
                    }
                }
                else if (header("DEMOSTART"))
                {
                    if (double.TryParse(common.Value, out var result))
                    {
                        Chart.Info.DemoStart = result;
                    }
                }
                else if (header("GENRE"))
                {
                    Chart.Info.Genre = common.Value;
                }
                else if (header("SONGVOL"))
                {
                    if (int.TryParse(common.Value, out var result))
                    {
                        Chart.Info.SongVol = result;
                    }
                }
                else if (header("SEVOL"))
                {
                    if (int.TryParse(common.Value, out var result))
                    {
                        Chart.Info.SeVol = result;
                    }
                }
                else if (header("SCOREMODE"))
                {
                    if (int.TryParse(common.Value, out var result))
                    {
                        switch (result)
                        {
                        case 0:
                            Chart.Info.ScoreMode = ScoreModes.Gen1;
                            break;

                        case 1:
                            Chart.Info.ScoreMode = ScoreModes.Gen2;
                            break;

                        case 2:
                            Chart.Info.ScoreMode = ScoreModes.Gen3;
                            break;

                        default:
                            Chart.Info.ScoreMode = ScoreModes.Gen3;
                            break;
                        }
                    }
                }
                else if (header("SIDE"))
                {
                    if (int.TryParse(common.Value, out var result))
                    {
                        switch (result)
                        {
                        case 0:
                            Chart.Info.Side = Sides.Normal;
                            break;

                        case 1:
                            Chart.Info.Side = Sides.Extra;
                            break;

                        case 2:
                            Chart.Info.Side = Sides.Both;
                            break;

                        default:
                            Chart.Info.Side = Sides.Both;
                            break;
                        }
                    }
                    else
                    {
                        switch (common.Value)
                        {
                        case "NORMAL":
                            Chart.Info.Side = Sides.Normal;
                            break;

                        case "EX":
                            Chart.Info.Side = Sides.Extra;
                            break;

                        case "BOTH":
                            Chart.Info.Side = Sides.Both;
                            break;

                        default:
                            Chart.Info.Side = Sides.Both;
                            break;
                        }
                    }
                }
                else if (header("LIFE"))
                {
                    if (int.TryParse(common.Value, out var result))
                    {
                        Chart.Info.Life = result;
                    }
                }
                else if (header("BGIMAGE"))
                {
                    Chart.Info.BgImage = common.Value;
                }
                else if (header("BGMOVIE"))
                {
                    Chart.Info.BgMovie = common.Value;
                }
                else if (header("MOVIEOFFSET"))
                {
                    if (double.TryParse(common.Value, out var result))
                    {
                        Chart.Info.MovieOffset = result;
                    }
                }
            }

            foreach (var course in Chart.Courses)
            {
                foreach (var item in course.Headers)
                {
                    bool header(string name)
                    {
                        return(name == item.Name.Trim());
                    }

                    if (header("COURSE"))
                    {
                        if (int.TryParse(item.Value, out var result))
                        {
                            course.Info.Course = CourseConverter.GetCoursesFromNumber(result);
                        }
                        else
                        {
                            course.Info.Course = CourseConverter.GetCoursesFromString(item.Value);
                        }
                    }
                    else if (header("LEVEL"))
                    {
                        if (int.TryParse(item.Value, out var result))
                        {
                            course.Info.Level = result;
                        }
                    }
                    else if (header("BALLOON"))
                    {
                        // 末尾の,対策
                        var split = item.Value.Split(new string[] { "," }, StringSplitOptions.RemoveEmptyEntries);
                        for (int i = 0; i < split.Length; i++)
                        {
                            if (int.TryParse(split[i], out var result))
                            {
                                course.Info.Balloon.Add(result);
                            }
                        }
                    }
                    else if (header("SCOREINIT"))
                    {
                        // ,で区切って、真打配点をしている場合がある
                        var split = item.Value.Split(new string[] { "," }, StringSplitOptions.RemoveEmptyEntries);
                        if (split.Length > 1)
                        {
                            // 真打あり
                            if (int.TryParse(split[0], out var result))
                            {
                                course.Info.ScoreInit = result;
                            }
                            if (int.TryParse(split[1], out var shin))
                            {
                                course.Info.ScoreInit_Shinuchi = shin;
                            }
                        }
                        else
                        {
                            if (int.TryParse(item.Value, out var result))
                            {
                                course.Info.ScoreInit = result;
                            }
                        }
                    }
                    else if (header("SCOREDIFF"))
                    {
                        if (int.TryParse(item.Value, out var result))
                        {
                            course.Info.ScoreDiff = result;
                        }
                    }
                    else if (header("STYLE"))
                    {
                        if (int.TryParse(item.Value, out var result))
                        {
                            switch (result)
                            {
                            case 1:
                                course.Info.Style = Styles.Single;
                                break;

                            case 2:
                                course.Info.Style = Styles.Double;
                                break;

                            default:
                                course.Info.Style = Styles.Single;
                                break;
                            }
                        }
                        else
                        {
                            switch (item.Value)
                            {
                            case "single":
                                course.Info.Style = Styles.Single;
                                break;

                            case "double":
                            case "couple":
                                course.Info.Style = Styles.Double;
                                break;

                            default:
                                course.Info.Style = Styles.Single;
                                break;
                            }
                        }
                    }
                    else if (header("EXAM1") || header("EXAM2") || header("EXAM3"))
                    {
                        var split = item.Value.Split(new string[] { "," }, StringSplitOptions.None);
                        var exam  = new Exam();
                        // 条件
                        if (split[0] != null)
                        {
                            switch (split[0])
                            {
                            case "g":
                                exam.Condition = Conditions.Gauge;
                                break;

                            case "jp":
                                exam.Condition = Conditions.JudgePerfect;
                                break;

                            case "jg":
                                exam.Condition = Conditions.JudgeGood;
                                break;

                            case "jb":
                                exam.Condition = Conditions.JudgeBad;
                                break;

                            case "s":
                                exam.Condition = Conditions.Score;
                                break;

                            case "r":
                                exam.Condition = Conditions.Roll;
                                break;

                            case "h":
                                exam.Condition = Conditions.Hit;
                                break;

                            case "c":
                                exam.Condition = Conditions.Combo;
                                break;

                            default:
                                exam.Condition = Conditions.Gauge;
                                break;
                            }
                        }
                        else
                        {
                            exam.Condition = Conditions.Gauge;
                        }
                        // 範囲
                        if (split[3] != null)
                        {
                            switch (split[3])
                            {
                            case "m":
                                exam.Scope = Scopes.More;
                                break;

                            case "l":
                                exam.Scope = Scopes.Less;
                                break;

                            default:
                                exam.Scope = Scopes.More;
                                break;
                            }
                        }
                        else
                        {
                            exam.Scope = Scopes.More;
                        }
                        // 赤合格
                        if (split[1] != null)
                        {
                            if (int.TryParse(split[1], out var result))
                            {
                                exam.RedValue = result;
                            }
                        }
                        else
                        {
                            exam.RedValue = 0;
                        }

                        // 金合格
                        if (split[2] != null)
                        {
                            if (int.TryParse(split[2], out var result))
                            {
                                exam.GoldValue = result;
                            }
                        }
                        else
                        {
                            exam.GoldValue = 0;
                        }

                        // 最後にEXAM何かを決めて、それに代入。
                        switch (item.Name.Trim())
                        {
                        case "EXAM1":
                            course.Info.Exam1 = exam;
                            break;

                        case "EXAM2":
                            course.Info.Exam2 = exam;
                            break;

                        case "EXAM3":
                            course.Info.Exam3 = exam;
                            break;

                        default:
                            throw new InvalidDataException();
                        }
                    }
                    else if (header("GAUGEINCR"))
                    {
                        switch (item.Value)
                        {
                        case "NORMAL":
                            course.Info.GaugeIncrease = Gauges.Normal;
                            break;

                        case "FLOOR":
                            course.Info.GaugeIncrease = Gauges.Floor;
                            break;

                        case "ROUND":
                            course.Info.GaugeIncrease = Gauges.Round;
                            break;

                        case "NOTFIX":
                            course.Info.GaugeIncrease = Gauges.NotFix;
                            break;

                        case "CEILING":
                            course.Info.GaugeIncrease = Gauges.Ceiling;
                            break;

                        default:
                            course.Info.GaugeIncrease = Gauges.Normal;
                            break;
                        }
                    }
                    else if (header("TOTAL"))
                    {
                        if (double.TryParse(item.Value, out var result))
                        {
                            course.Info.Total = result;
                        }
                    }
                    else if (header("HIDDENBRANCH"))
                    {
                        if (!string.IsNullOrEmpty(item.Value))
                        {
                            course.Info.HiddenBranch = true;
                        }
                        else
                        {
                            course.Info.HiddenBranch = false;
                        }
                    }
                }
            }

            // 各譜面のパース処理。
            foreach (var course in Chart.Courses)
            {
                void parseTJA(List <Chip> list, IReadOnlyList <string> measures)
                {
                    var  nowTime                  = (long)(Chart.Info.Offset * 1000 * 1000.0) * -1;
                    var  nowScroll                = 1.0D;
                    var  nowBPM                   = Chart.Info.BPM;
                    var  gogoTime                 = false;
                    var  branching                = false;
                    var  nowBranch                = Branches.Normal;
                    var  nowMeasure               = new Measure(4, 4);
                    var  measureCount             = 0;
                    var  branchBeforeMeasureCount = 0;
                    var  branchBeforeTime         = 0L;
                    var  branchCount              = 0;
                    var  branchAfterMeasure       = 0;
                    var  balloonIndex             = 0;
                    Chip rollBegin                = null;

                    var bgm = new Chip();

                    bgm.ChipType = Chips.BGMStart;
                    bgm.Time     = 0;
                    list.Add(bgm);
                    foreach (var measure in measures)
                    {
                        var nowMeasureNotes = 0;
                        var measureAdded    = false;
                        // まずはその小節にある音符数(空白含む)を調べる
                        foreach (var line in measure.Split(new string[] { NewLine }, StringSplitOptions.RemoveEmptyEntries))
                        {
                            if (!line.StartsWith("#"))
                            {
                                nowMeasureNotes += line.Length;
                            }
                        }


                        // 実際にListにブチ込んでいく
                        foreach (var line in measure.Split(new string[] { NewLine }, StringSplitOptions.RemoveEmptyEntries))
                        {
                            // 小節開始時の一つの音符あたりの時間
                            // わざわざ文字の度再計算させてるけど仕方ないな!
                            var timePerNotes = (long)(nowMeasure.GetRate() / nowBPM / nowMeasureNotes * 1000 * 1000.0);


                            if (!line.StartsWith("#"))
                            {
                                if (!measureAdded)
                                {
                                    //小節線
                                    var measureChip = new Chip();
                                    measureChip.ChipType     = Chips.Measure;
                                    measureChip.IsHitted     = false;
                                    measureChip.IsGoGoTime   = gogoTime;
                                    measureChip.CanShow      = true;
                                    measureChip.Scroll       = nowScroll;
                                    measureChip.Branch       = nowBranch;
                                    measureChip.Branching    = branching;
                                    measureChip.Time         = nowTime;
                                    measureChip.Scroll       = nowScroll;
                                    measureChip.BPM          = nowBPM;
                                    measureChip.MeasureCount = measureCount;
                                    measureChip.Measure      = nowMeasure;
                                    // Listへ
                                    list.Add(measureChip);
                                    measureAdded = true;
                                }
                                // 音符
                                foreach (var note in line)
                                {
                                    var chip = new Chip();
                                    chip.ChipType     = Chips.Note;
                                    chip.NoteType     = NotesConverter.GetNotesFromChar(note);
                                    chip.IsHitted     = false;
                                    chip.IsGoGoTime   = gogoTime;
                                    chip.CanShow      = true;
                                    chip.Scroll       = nowScroll;
                                    chip.Branch       = nowBranch;
                                    chip.Branching    = branching;
                                    chip.Time         = nowTime;
                                    chip.Scroll       = nowScroll;
                                    chip.BPM          = nowBPM;
                                    chip.MeasureCount = measureCount;
                                    chip.Measure      = nowMeasure;

                                    if (chip.NoteType == Notes.Balloon)
                                    {
                                        // ふうせん連打のノルマ
                                        chip.RollCount = course.Info.Balloon[balloonIndex];
                                        balloonIndex++;
                                    }

                                    if (chip.NoteType == Notes.Balloon || chip.NoteType == Notes.RollStart || chip.NoteType == Notes.ROLLStart)
                                    {
                                        // 連打
                                        // 始点を記憶しておく
                                        rollBegin = chip;
                                    }

                                    if (chip.NoteType == Notes.RollEnd)
                                    {
                                        // 連打終端
                                        if (rollBegin != null)
                                        {
                                            rollBegin.RollEnd = chip;
                                        }
                                        rollBegin = null;
                                    }

                                    // ひとつ進める
                                    nowTime += timePerNotes;

                                    // Listへ
                                    list.Add(chip);
                                }
                            }
                            else
                            {
                                // 命令
                                bool command(string name)
                                {
                                    return(line.Trim().StartsWith(name));
                                }

                                var chip = new Chip();
                                chip.IsHitted = false;
                                chip.CanShow  = false;

                                var trimed = line.Trim();

                                if (command("#MEASURE"))
                                {
                                    var param = trimed.Substring("#MEASURE".Length).Trim();
                                    var split = param.Split(new string[] { "/" }, StringSplitOptions.None);
                                    // 再計算は自動的にされるから問題ない。
                                    if (!string.IsNullOrWhiteSpace(split[0]))
                                    {
                                        nowMeasure.Part = Convert.ToDouble(split[0]);
                                    }
                                    if (!string.IsNullOrWhiteSpace(split[1]))
                                    {
                                        nowMeasure.Beat = Convert.ToDouble(split[1]);
                                    }
                                    chip.ChipType = Chips.MeasureChange;
                                }
                                else if (command("#BPMCHANGE"))
                                {
                                    var param = trimed.Substring("#BPMCHANGE".Length).Trim();
                                    if (!string.IsNullOrWhiteSpace(param))
                                    {
                                        nowBPM = Convert.ToDouble(param);
                                    }
                                    chip.ChipType = Chips.BPMChange;
                                }
                                else if (command("#DELAY"))
                                {
                                    var param = trimed.Substring("#DELAY".Length).Trim();
                                    var delay = 0L;
                                    if (!string.IsNullOrWhiteSpace(param))
                                    {
                                        delay = Convert.ToInt64(Convert.ToDouble(param) * 1000 * 1000.0);
                                    }
                                    nowTime += delay;
                                }
                                else if (command("#SCROLL"))
                                {
                                    var param = trimed.Substring("#SCROLL".Length).Trim();
                                    if (!string.IsNullOrWhiteSpace(param))
                                    {
                                        nowScroll = Convert.ToDouble(param);
                                    }
                                    chip.ChipType = Chips.ScrollChange;
                                }
                                else if (command("#GOGOSTART"))
                                {
                                    gogoTime      = true;
                                    chip.ChipType = Chips.GoGoStart;
                                }
                                else if (command("#GOGOEND"))
                                {
                                    gogoTime      = false;
                                    chip.ChipType = Chips.GoGoEnd;
                                }
                                else if (command("#SECTION"))
                                {
                                    // シミュレータ側で実装するのでここでは特に何も無い。
                                    chip.ChipType = Chips.Section;
                                }
                                else if (command("#BRANCHSTART"))
                                {
                                    // #BRANCHSTART <type>,expert,master
                                    chip.ChipType = Chips.BranchStart;
                                    branching     = true;

                                    branchBeforeMeasureCount = measureCount;
                                    branchBeforeTime         = nowTime;
                                    branchCount = 0;

                                    // 1小節前に分岐するフックを入れる。
                                    var beforeMeasure     = GetBeforeMeasureFromList(list, list.Count);
                                    var beforeMeasureChip = new Chip();
                                    beforeMeasureChip.ChipType   = Chips.Branching;
                                    beforeMeasureChip.BPM        = list[beforeMeasure].BPM;
                                    beforeMeasureChip.Scroll     = list[beforeMeasure].Scroll;
                                    beforeMeasureChip.Time       = list[beforeMeasure].Time;
                                    beforeMeasureChip.IsGoGoTime = list[beforeMeasure].IsGoGoTime;
                                    list.Insert(beforeMeasure, beforeMeasureChip);
                                }
                                else if (command("#BRANCHEND"))
                                {
                                    // 時間を……元に戻すッ!!!
                                    nowTime      = branchBeforeTime;
                                    measureCount = branchAfterMeasure;

                                    chip.ChipType = Chips.BranchStart;
                                    branching     = false;
                                }
                                else if (command("#N") || command("#E") || command("#M"))
                                {
                                    var type = trimed.Substring(0, 2);
                                    if (!string.IsNullOrWhiteSpace(type))
                                    {
                                        // 時間を……元に戻すッ!!!
                                        nowTime = branchBeforeTime;
                                        branchCount++;
                                        // 一番初めに書かれた譜面分岐の小節数を保持
                                        if (branchCount == 2)
                                        {
                                            branchAfterMeasure = measureCount;
                                        }
                                        measureCount = branchBeforeMeasureCount;
                                        switch (type)
                                        {
                                        case "#N":
                                            nowBranch = Branches.Normal;
                                            break;

                                        case "#E":
                                            nowBranch = Branches.Expert;
                                            break;

                                        case "#M":
                                            nowBranch = Branches.Master;
                                            break;
                                        }
                                    }
                                }
                                else if (command("#LEVELHOLD"))
                                {
                                    // シミュレータ側で実装するのでここでは特に何も無い。
                                    chip.ChipType = Chips.LevelHold;
                                }

                                // 共通
                                chip.IsGoGoTime   = gogoTime;
                                chip.Scroll       = nowScroll;
                                chip.BPM          = nowBPM;
                                chip.Branch       = nowBranch;
                                chip.Branching    = branching;
                                chip.Time         = nowTime;
                                chip.MeasureCount = measureCount;

                                list.Add(chip);
                            }
                            measureCount++;
                        }
                    }
                }

                parseTJA(course.Chip.Common, course.Measure.Common);
                SENoteGenerator.GenerateSENotes(course.Chip.Common);
                parseTJA(course.Chip.Player1, course.Measure.Player1);
                SENoteGenerator.GenerateSENotes(course.Chip.Player1);
                parseTJA(course.Chip.Player2, course.Measure.Player2);
                SENoteGenerator.GenerateSENotes(course.Chip.Player2);
            }
        }
Ejemplo n.º 2
0
        public static Playable Parse(OpenTaikoChartInfomation otci, OpenTaikoChartCourse otcc)
        {
            // メドレーではないので大きさは1。
            var playable = new Playable
            {
                Sections = new List <Chip> [1]
            };
            var sections = playable.Sections;

            sections[0] = new List <Chip>();
            var balloonIndex = 0;

            var courseJson = otcc;

            // パースする。
            var list = sections[0];

            // 初めから1秒開けておく。

            var  nowTime              = 0L;
            var  nowBPM               = otci.BPM ?? 120.0;
            var  nowMeasure           = new Measure(4, 4);
            var  nowScroll            = 1.0;
            var  isGoGoTime           = false;
            var  measureCount         = 0;
            var  isFirstNoteInMeasure = true;
            var  movieOffset          = otci.Movieoffset;
            var  barVisible           = true;
            Chip rollstartChip        = null;

            // オフセットする。
            var offset = otci.Offset ?? 0;

            // オフセットが0未満だったらBGMの開始を0にしてそれ以降をずらす。
            // そうでないならそのままオフセットする。
            if (offset < 0)
            {
                //
                //var oneMeasure = GetMeasureDuration(nowMeasure, nowBPM);
                var bgmStartChip = new Chip
                {
                    ChipType = Chips.BGMStart,
                    Time     = nowTime - (long)(Math.Abs(offset) * 1000.0 * 1000.0),
                    BPM      = nowBPM
                };
                list.Add(bgmStartChip);
            }
            else
            {
                var bgmStartChip = new Chip
                {
                    ChipType = Chips.BGMStart,
                    Time     = nowTime,
                    BPM      = nowBPM
                };
                list.Add(bgmStartChip);
                // その時間分ずらす
                nowTime += (long)(offset * 1000.0 * 1000.0);
            }

            var origin = list.Where(c => c.ChipType == Chips.BGMStart).First();

            foreach (var measure in courseJson.Measures)
            {
                // その小節にいくつ音符があるかカウントする
                var notesCount        = 0;
                var notesElementCount = 0;
                foreach (var line in measure)
                {
                    if (!line.Trim().StartsWith("#") && !string.IsNullOrWhiteSpace(line.Trim()))
                    {
                        // 命令行ではない
                        notesElementCount++;
                        foreach (var digit in line)
                        {
                            if (digit >= '0' && digit <= '9')
                            {
                                // 数字だ
                                notesCount++;
                            }
                        }
                    }
                }

                foreach (var line in measure)
                {
                    // 行
                    // ひとつの音符あたりの数
                    var timePerNotes = (long)(GetMeasureDuration(nowMeasure, nowBPM) / notesCount);

                    if (!line.Trim().StartsWith("#"))
                    {
                        // 命令行ではない
                        if (isFirstNoteInMeasure)
                        {
                            // 小節
                            var measureChip = new Chip
                            {
                                ChipType     = Chips.Measure,
                                CanShow      = barVisible,
                                Scroll       = nowScroll,
                                BPM          = nowBPM,
                                IsGoGoTime   = isGoGoTime,
                                Measure      = nowMeasure,
                                MeasureCount = measureCount,
                                Time         = nowTime
                            };
                            list.Add(measureChip);

                            isFirstNoteInMeasure = false;
                        }
                        foreach (var digit in line)
                        {
                            if (digit >= '0' && digit <= '9')
                            {
                                // 数字だ
                                var note = NotesConverter.GetNotesFromChar(digit);

                                // 音符の追加
                                var noteChip = new Chip
                                {
                                    ChipType     = Chips.Note,
                                    NoteType     = note,
                                    Scroll       = nowScroll,
                                    BPM          = nowBPM,
                                    CanShow      = true,
                                    IsGoGoTime   = isGoGoTime,
                                    Measure      = nowMeasure,
                                    MeasureCount = measureCount,
                                    Time         = nowTime
                                };

                                if (note == Notes.RollStart || note == Notes.ROLLStart || note == Notes.Balloon)
                                {
                                    // 連打開始なので記憶しておく。
                                    rollstartChip = noteChip;

                                    if (note == Notes.Balloon)
                                    {
                                        if (courseJson.Balloon.Length > balloonIndex)
                                        {
                                            noteChip.RollObjective = courseJson.Balloon[balloonIndex] ?? 5;
                                            balloonIndex++;
                                        }
                                        else
                                        {
                                            noteChip.RollObjective = 5;
                                        }
                                    }
                                }
                                else if (note == Notes.RollEnd)
                                {
                                    // 連打開始なので記憶した連打をrollendにり当てる。
                                    if (rollstartChip != null)
                                    {
                                        rollstartChip.RollEnd = noteChip;
                                        rollstartChip         = null;
                                    }
                                }

                                list.Add(noteChip);

                                // 時間をひとつすすめる
                                nowTime += timePerNotes;
                            }
                        }
                    }
                    else
                    {
                        // 命令行
                        var eventChip = new Chip();

                        var command = line.Trim();
                        var param   = command.IndexOf(' ') >= 0 ? command.Substring(command.IndexOf(' ')) : "";

                        bool commandMatch(string name)
                        {
                            return(command.StartsWith(name, StringComparison.InvariantCultureIgnoreCase));
                        }

                        if (commandMatch("#bpm"))
                        {
                            // #bpm n1
                            if (double.TryParse(param, out var bpm))
                            {
                                eventChip.ChipType = Chips.BPMChange;
                                nowBPM             = bpm;
                            }
                            else
                            {
                                continue;
                            }
                        }
                        else if (commandMatch("#scroll"))
                        {
                            // #scroll n1
                            if (double.TryParse(param, out var scroll))
                            {
                                eventChip.ChipType = Chips.ScrollChange;
                                nowScroll          = scroll;
                            }
                            else
                            {
                                continue;
                            }
                        }
                        else if (commandMatch("#tsign"))
                        {
                            // #tsign n1/n2
                            var n1 = param.Substring(0, param.IndexOf('/'));
                            var n2 = param.Substring(param.IndexOf('/') + 1);

                            if (double.TryParse(n1, out var part) && double.TryParse(n2, out var beat))
                            {
                                eventChip.ChipType = Chips.MeasureChange;
                                nowMeasure         = new Measure(part, beat);
                            }
                            else
                            {
                                continue;
                            }
                        }
                        else if (commandMatch("#gogobegin"))
                        {
                            eventChip.ChipType = Chips.GoGoStart;
                            isGoGoTime         = true;
                        }
                        else if (commandMatch("#gogoend"))
                        {
                            eventChip.ChipType = Chips.GoGoEnd;
                            isGoGoTime         = false;
                        }
                        else if (commandMatch("#delay"))
                        {
                            // #delay n1
                            if (double.TryParse(param, out var delay))
                            {
                                nowTime += (long)(delay * 1000.0 * 1000.0);
                            }
                            else
                            {
                                continue;
                            }
                        }
                        else if (commandMatch("#bar"))
                        {
                            // #bar show/hide
                            var visible = param.Trim();
                            if (visible == "show")
                            {
                                barVisible = true;
                            }
                            else if (visible == "hide")
                            {
                                barVisible = false;
                            }
                            else
                            {
                                continue;
                            }
                        }
                        else
                        {
                            continue;
                        }

                        eventChip.Scroll       = nowScroll;
                        eventChip.BPM          = nowBPM;
                        eventChip.IsGoGoTime   = isGoGoTime;
                        eventChip.Measure      = nowMeasure;
                        eventChip.MeasureCount = measureCount;
                        eventChip.Time         = nowTime;
                        list.Add(eventChip);
                    }
                }

                // ノーツが空だったときの処理
                if (notesElementCount <= 0)
                {
                    // 小節
                    var measureChip = new Chip
                    {
                        ChipType     = Chips.Measure,
                        CanShow      = barVisible,
                        Scroll       = nowScroll,
                        BPM          = nowBPM,
                        IsGoGoTime   = isGoGoTime,
                        Measure      = nowMeasure,
                        MeasureCount = measureCount,
                        Time         = nowTime
                    };
                    list.Add(measureChip);

                    nowTime += (long)GetMeasureDuration(nowMeasure, nowBPM);
                }


                measureCount++;
                isFirstNoteInMeasure = true;
            }

            // 後処理。

            // 動画の開始時間を表すチップを挿入する。
            if (movieOffset.HasValue)
            {
                var offsetValue = movieOffset.Value;
                // 0未満だったら、全体をずらす必要がある。
                if (offsetValue < 0)
                {
                    // すべてのチップの時間をずらす
                    list.ForEach(c => c.Time += (long)(Math.Abs(offsetValue) * 1000.0 * 1000.0));

                    var offsetChip = new Chip
                    {
                        ChipType = Chips.MovieStart,
                        Time     = origin.Time - (long)(Math.Abs(offsetValue) * 1000.0 * 1000.0)
                    };
                    //list.Add(offsetChip);
                    var nearestChip = list.Where(c => c.Time <= offsetChip.Time);
                    list.Insert(nearestChip.Count() > 0 ? list.IndexOf(nearestChip.Last()) + 1 : 0, offsetChip);
                }
                else
                {
                    // そのまま入れることができる。
                    var offsetChip = new Chip
                    {
                        ChipType = Chips.MovieStart,
                        Time     = origin.Time + (long)(Math.Abs(offsetValue) * 1000.0 * 1000.0)
                    };
                    var nearestChip = list.Where(c => c.Time <= offsetChip.Time);
                    list.Insert(nearestChip.Count() > 0 ? list.IndexOf(nearestChip.Last()) + 1 : 0, offsetChip);
                }
            }

            // オフセットを追加する。前に1小節、後ろに1小節。
            {
                // 3秒。
                var offsetTime = 3L * 1000 * 1000;
                {
                    // 前
                    var origBPM = origin.BPM;
                    list.ForEach(c => c.Time += offsetTime);
                }
                {
                    // 後
                    var last     = list.Last();
                    var lastChip = new Chip
                    {
                        BPM      = last.BPM,
                        Scroll   = last.Scroll,
                        CanShow  = false,
                        ChipType = Chips.Measure,
                        Time     = last.Time + offsetTime
                    };

                    list.Add(lastChip);
                }
            }

            return(playable);
        }