Beispiel #1
0
 public AddLongNoteOperation(Model model, AirHold airHold, AirableNote airable)
 {
     Invoke += () =>
     {
         model.NoteBook.AttachAirHoldToAirableNote(airable, airHold, null);
     };
     Undo += () =>
     {
         model.NoteBook.DetachAirHoldFromAirableNote(airable, out airHold);
     };
 }
Beispiel #2
0
 public AddStepNoteOperation(AirHold airHold, AirAction airAction)
 {
     Invoke += () =>
     {
         airHold.Add(airAction);
     };
     Undo += () =>
     {
         airHold.Remove(airAction);
     };
 }
Beispiel #3
0
        private void DrawNote(PaintEventArgs e)
        {
            switch (noteType)
            {
            case NoteType.TAP:
                Tap.Draw(e, new PointF(previewBox.Width / 2f, previewBox.Height / 2f), new SizeF(80, 10));
                break;

            case NoteType.EXTAP:
                ExTap.Draw(e, new PointF(previewBox.Width / 2f, previewBox.Height / 2f), new SizeF(80, 10));
                break;

            case NoteType.EXTAPDOWN:
                ExTapDown.Draw(e, new PointF(previewBox.Width / 2f, previewBox.Height / 2f), new SizeF(80, 10));
                break;

            case NoteType.AWEXTAP:
                AwesomeExTap.Draw(e, new PointF(previewBox.Width / 2f, previewBox.Height / 2f), new SizeF(80, 10));
                break;

            case NoteType.FLICK:
                Flick.Draw(e, new PointF(previewBox.Width / 2f, previewBox.Height / 2f), new SizeF(80, 10));
                break;

            case NoteType.HELL:
                HellTap.Draw(e, new PointF(previewBox.Width / 2f, previewBox.Height / 2f), new SizeF(80, 10));
                break;

            case NoteType.HOLD:
                Hold.Draw(e, new PointF(previewBox.Width / 2f, previewBox.Height / 2f - 25), new SizeF(70, 50));
                HoldBegin.Draw(e, new PointF(previewBox.Width / 2f, previewBox.Height / 2f), new SizeF(80, 10));
                break;

            case NoteType.SLIDE:
                Slide.Draw(e, new PointF(previewBox.Width / 2f, previewBox.Height / 2f - 25), new SizeF(70, 50));
                SlideBegin.Draw(e, new PointF(previewBox.Width / 2f, previewBox.Height / 2f), new SizeF(80, 10));
                break;

            case NoteType.SLIDECURVE:
                SlideCurve.Draw(e, new PointF(previewBox.Width / 2f, previewBox.Height / 2f), new SizeF(80, 10));
                break;

                #region Air系の描画
            //HACK: いろいろとコードがガバガバなので後で直してもええんやで...
            case NoteType.AIRUPL:
                new AirUpL(8, new Position(), new PointF(previewBox.Width / 2f - 4 * 12, previewBox.Height / 2f + 15), -1).Draw(e.Graphics, new Point());
                break;

            case NoteType.AIRUPC:
                new AirUpC(8, new Position(), new PointF(previewBox.Width / 2f - 4 * 12, previewBox.Height / 2f + 15), -1).Draw(e.Graphics, new Point());
                break;

            case NoteType.AIRUPR:
                new AirUpR(8, new Position(), new PointF(previewBox.Width / 2f - 4 * 12, previewBox.Height / 2f + 15), -1).Draw(e.Graphics, new Point());
                break;

            case NoteType.AIRDOWNL:
                new AirDownL(8, new Position(), new PointF(previewBox.Width / 2f - 4 * 12, previewBox.Height / 2f + 15), -1).Draw(e.Graphics, new Point());
                break;

            case NoteType.AIRDOWNC:
                new AirDownC(8, new Position(), new PointF(previewBox.Width / 2f - 4 * 12, previewBox.Height / 2f + 15), -1).Draw(e.Graphics, new Point());
                break;

            case NoteType.AIRDOWNR:
                new AirDownR(8, new Position(), new PointF(previewBox.Width / 2f - 4 * 12, previewBox.Height / 2f + 15), -1).Draw(e.Graphics, new Point());
                break;

                #endregion
            case NoteType.AIRHOLD:
                AirHold.Draw(e, new PointF(previewBox.Width / 2f, previewBox.Height / 2f + 25), new SizeF(7, 50));
                AirAction.Draw(e, new PointF(previewBox.Width / 2f, previewBox.Height / 2f), new SizeF(78, 8));
                break;

            // HACK: 色や座標に即値を使っているのでよくない
            case NoteType.BPM:
                e.Graphics.DrawLine(
                    Pens.LimeGreen,
                    (previewBox.Width - 100) / 2f,
                    previewBox.Height / 2f,
                    (previewBox.Width + 100) / 2f,
                    previewBox.Height / 2f);
                break;

            // HACK: 同上
            case NoteType.HIGHSPEED:
                e.Graphics.DrawLine(
                    Pens.Red,
                    (previewBox.Width - 100) / 2f,
                    previewBox.Height / 2f,
                    (previewBox.Width + 100) / 2f,
                    previewBox.Height / 2f);
                break;

            default:
                break;
            }
        }
Beispiel #4
0
        private readonly Regex _arrow   = CompileRule("mmm5x: notes");           //Hex bar, B36 startLane, notes


        public Song ParseSong(string filename)
        {
            var theSourceFile = new FileInfo(filename);
            var reader        = theSourceFile.OpenText();

            var data = new List <string>();

            while (true)
            {
                string line = reader.ReadLine();
                if (line == null)
                {
                    break;
                }
                if (line.StartsWith("#"))
                {
                    data.Add(line.Substring(1));
                }
            }

            bool[] hasBeenRead = new bool[data.Count];

            int  ticksPerBeat = 480; //default
            int  beatsPerBar  = 4;   //default TODO this is being ignored! should be slowing down approach rate/effective BPM when increased
            int  barOffset    = 0;   //default
            Song song         = new Song();


            var barSections    = new List <BarSection>();
            var bpmDefinitions = new Dictionary <int, double>();//BPM ID -> BPM value

            string difficulty = null;

            //PASS 1: construct base rhythm model
            for (int i = 0; i < data.Count; i++)
            {
                if (hasBeenRead[i])
                {
                    continue;
                }
                var line = data[i];
                //assume we read this line (simplifies our code a bit)
                hasBeenRead[i] = true;

                //now try all regex rules for this stage in sequence:

                var match = _barLength.Match(line);
                if (match.Success)
                {
                    barSections.Add(new BarSection(ParseMmm(match.Groups[1]), ParseInt(match.Groups[2])));
                    continue;
                }

                match = _bpmDef.Match(line);
                if (match.Success)
                {
                    bpmDefinitions.Add(ParseZz(match.Groups[1]), ParseDouble(match.Groups[2]));
                    continue;
                }

                //also parse song info while we're at it:

                match = _wave.Match(line);
                if (match.Success)
                {
                    song.AudioFile = match.Groups[1].Value;
                    continue;
                }

                match = _waveOffset.Match(line);
                if (match.Success)
                {
                    song.Offset = ParseDouble(match.Groups[1]);
                    continue;
                }

                //reach here, nothing matched, so we didn't read this line after all
                hasBeenRead[i] = false;
            }

            //We now have enough information to construct the Bar->Beat converter
            var barToBeat = BpmGraph.ParseBars(barSections);

            var bpmChanges = new List <BeatEvent>();

            //PASS 2: construct internal rhythm model
            for (int i = 0; i < data.Count; i++)
            {
                if (hasBeenRead[i])
                {
                    continue;
                }
                var line = data[i];
                //assume we read this line (simplifies our code a bit)
                hasBeenRead[i] = true;

                //now try all regex rules for this stage in sequence:

                var match = _bpmChange.Match(line);
                if (match.Success)
                {
                    bpmChanges.Add(new BeatEvent(barToBeat.BeatAt(ParseMmm(match.Groups[1])), bpmDefinitions[ParseInt(match.Groups[2])]));
                    continue;
                }

                //reach here, nothing matched, so we didn't read this line after all
                hasBeenRead[i] = false;
            }
            //We can now construct our BPM Graph, which means we now have everything needed to start parsing notes.
            bpmChanges.Sort((a, b) => a.beat.CompareTo(b.beat));
            BpmGraph bpmGraph = BpmGraph.ParseBpm(bpmChanges, Array.Empty <BeatEvent>());

            var notes = new List <RhythmEvent>();

            var holdData  = new Dictionary <int, List <PartialData> >();
            var slideData = new Dictionary <int, List <PartialData> >();
            var airData   = new Dictionary <int, List <PartialData> >();

            //PASS 3: read all note data
            for (int i = 0; i < data.Count; i++)
            {
                if (hasBeenRead[i])
                {
                    continue;
                }
                var line = data[i];
                //assume we read this line (simplifies our code a bit)
                hasBeenRead[i] = true;

                //now try all regex rules for this stage in sequence:

                var match = _tap.Match(line);
                if (match.Success)
                {
                    int barNum = ParseMmm(match.Groups[1]) + barOffset;
                    int lane   = ParseXY(match.Groups[2]);
                    ProcessNotes(match.Groups[3], (barOff, type, width) =>
                    {
                        double beat = barToBeat.BeatAt(barNum + barOff);
                        var time    = bpmGraph.FromBeat(beat);
                        var pos     = new LanePosition(lane * 2, width * 2);
                        switch (type)
                        {
                        case 1:                    //standard
                            notes.Add(new SimpleNote(time, pos, false));
                            break;

                        case 2:                    //golden
                            notes.Add(new SimpleNote(time, pos, true));
                            break;

                        case 3:                    //swipe
                            notes.Add(new SwipeNote(time, pos));
                            break;

                        case 4:                    //mine
                            notes.Add(new Mine(time, pos));
                            break;

                        //TODO types 5 & 6
                        default:
                            Debug.LogError($"Ignoring unknown tap type {type} at time {time} position {pos}");
                            break;
                        }
                    });


                    continue;
                }

                match = _arrow.Match(line);
                if (match.Success)
                {
                    int barNum = ParseMmm(match.Groups[1]) + barOffset;
                    int lane   = ParseXY(match.Groups[2]);
                    ProcessNotes(match.Groups[3], (barOff, type, width) =>
                    {
                        double beat    = barToBeat.BeatAt(barNum + barOff);
                        var time       = bpmGraph.FromBeat(beat);
                        var pos        = new LanePosition(lane * 2, width * 2);
                        bool isUp      = true;
                        int arrowShift = 0;
                        //convert the 6 cases to up/down/left/right/center information
                        switch (type)
                        {
                        case 1:
                            isUp       = true;
                            arrowShift = 0;
                            break;

                        case 2:
                            isUp       = false;
                            arrowShift = 0;
                            break;

                        case 3:
                            isUp       = true;
                            arrowShift = -1;
                            break;

                        case 4:
                            isUp       = true;
                            arrowShift = +1;
                            break;

                        case 5:
                            isUp       = false;
                            arrowShift = +1;                        //inverted because down
                            break;

                        case 6:
                            isUp       = false;
                            arrowShift = -1;                        //inverted because down
                            break;

                        default:
                            Debug.LogError($"Unknown Air Arrow {type} at time {time} position {pos}");
                            break;
                        }
                        notes.Add(new AirArrow(time, pos, isUp, arrowShift));
                    });

                    continue;
                }
                //matching holds, airholds and slides are basically exactly the same, the only thing that changes is the output array

                match = _hold.Match(line);
                if (match.Success)
                {
                    ProcessChannelNotes(match, barOffset, barToBeat, bpmGraph, holdData);
                    continue;
                }

                match = _airHold.Match(line);
                if (match.Success)
                {
                    ProcessChannelNotes(match, barOffset, barToBeat, bpmGraph, airData);
                    continue;
                }

                match = _slide.Match(line);
                if (match.Success)
                {
                    ProcessChannelNotes(match, barOffset, barToBeat, bpmGraph, slideData);
                    continue;
                }

                //reach here, nothing matched, so we didn't read this line after all
                hasBeenRead[i] = false;
            }

            //all note data has now been read. We'll now assemble all multi-part notes.

            //sort note data and store length so we can use this for lookups on start/end of holds (TODO)
            notes.Sort((a, b) => a.Time.Beats.CompareTo(b.Time.Beats));
            int noteDataLength = notes.Count;

            //TODO a lot of code below for (air)holds/slides is shared, may be useful to combine these into a generic function

            //holds:
            foreach (var holdChannel in holdData.Values)
            {
                //first sort all hold data in this channel
                holdChannel.Sort((a, b) => a.Time.Beats.CompareTo(b.Time.Beats));
                //now walk through each in sequence with a state machine to assemble the holds

                PartialData?startingPoint = null;

                foreach (var dataPoint in holdChannel)
                {
                    switch (dataPoint.Type)
                    {
                    case 1:                //start
                        if (startingPoint != null)
                        {
                            Debug.LogError($"Hold started on channel with already active hold at {startingPoint?.Time}");
                        }
                        startingPoint = dataPoint;
                        break;

                    case 2:                //end
                        if (startingPoint == null)
                        {
                            Debug.LogError($"Attempt to end hold at {dataPoint.Time} without a starting point.");
                            continue;
                        }

                        var holdNote = new HoldNote(startingPoint.Value.Time, dataPoint.Pos, new SlidePoint[1]);
                        holdNote.SlidePoints[0] = new SlidePoint(holdNote, dataPoint.Time, holdNote.Position);
                        notes.Add(holdNote);
                        startingPoint = null;
                        break;

                    case 3:                                             //relay
                        Debug.LogWarning($"Skipping hold relay point"); //TODO (will be fixed if we merge slide and hold parsing code)
                        break;

                    default:
                        Debug.LogError($"Unknown hold point type {dataPoint.Type} found at {dataPoint.Time}");
                        break;
                    }
                }
            }

            //air holds:
            foreach (var airChannel in airData.Values)
            {
                //first sort all hold data in this channel
                airChannel.Sort((a, b) => a.Time.Beats.CompareTo(b.Time.Beats));
                //now walk through each in sequence with a state machine to assemble the holds

                PartialData?startingPoint = null;

                foreach (var dataPoint in airChannel)
                {
                    switch (dataPoint.Type)
                    {
                    case 1:                //start
                        if (startingPoint != null)
                        {
                            Debug.LogError($"Air hold started on channel with already active hold at {startingPoint?.Time}");
                        }
                        startingPoint = dataPoint;
                        break;

                    case 2:                //end
                        if (startingPoint == null)
                        {
                            Debug.LogError($"Attempt to end air hold at {dataPoint.Time} without a starting point.");
                            continue;
                        }

                        var airHold = new AirHold(startingPoint.Value.Time, dataPoint.Pos, new SlidePoint[1]);
                        airHold.SlidePoints[0] = new SlidePoint(airHold, dataPoint.Time, airHold.Position);
                        notes.Add(airHold);
                        notes.Add(new AirAction(dataPoint.Time, dataPoint.Pos));
                        startingPoint = null;
                        break;

                    case 3:                //relay
                        notes.Add(new AirAction(dataPoint.Time, dataPoint.Pos));
                        break;

                    default:
                        Debug.LogError($"Unknown hold point type {dataPoint.Type} found at {dataPoint.Time}");
                        break;
                    }
                }
            }

            //slides:
            foreach (var slideChannel in slideData.Values)
            {
                //first sort all hold data in this channel
                slideChannel.Sort((a, b) => a.Time.Beats.CompareTo(b.Time.Beats));
                //now walk through each in sequence with a state machine to assemble the holds

                var dataPoints = new List <PartialData>();

                foreach (var dataPoint in slideChannel)
                {
                    switch (dataPoint.Type)
                    {
                    case 1:                //start
                        if (dataPoints.Count > 0)
                        {
                            Debug.LogError($"Hold started on channel with already active slide at {dataPoints[0].Time}");
                        }
                        dataPoints.Add(dataPoint);
                        break;

                    case 2:                //end
                        if (dataPoints.Count == 0)
                        {
                            Debug.LogError($"Attempt to end slide at {dataPoint.Time} without a starting point.");
                            continue;
                        }
                        dataPoints.Add(dataPoint);
                        //we now have all points, create a SlideNote from this:
                        var  slidePoints             = new List <SlidePoint>();
                        var  slideNote               = new SlideNote(dataPoints[0].Time, dataPoints[0].Pos, null);
                        Note previous                = slideNote;
                        SlidePoint.AnchorNote anchor = null;
                        for (int i = 1; i < dataPoints.Count; i++)                    //start from 1, the initial point is the base SlideNote
                        {
                            var pointHere = dataPoints[i];
                            if (pointHere.Type == 4)
                            {
                                //anchor point
                                anchor = new SlidePoint.AnchorNote(pointHere.Time, pointHere.Pos);
                                continue;
                            }

                            var newPoint = new SlidePoint(previous, pointHere.Time, pointHere.Pos);
                            newPoint.AnchorPoint = anchor;                        //writes anchor if set, null otherwise
                            anchor           = null;
                            newPoint.Visible = pointHere.Type == 3;               //set visible/invisible
                            slidePoints.Add(newPoint);
                            previous = newPoint;
                        }
                        slideNote.SlidePoints = slidePoints.ToArray();
                        notes.Add(slideNote);
                        dataPoints.Clear();
                        break;

                    case 3:                //relay
                    case 4:                //bezier anchor
                    case 5:                //invisible relay
                        if (dataPoints.Count == 0)
                        {
                            Debug.LogError($"Attempt to add points to slide at {dataPoint.Time} without a starting point.");
                            continue;
                        }
                        dataPoints.Add(dataPoint);
                        break;

                    default:
                        Debug.LogError($"Unknown slide point type {dataPoint.Type} found at {dataPoint.Time}");
                        break;
                    }
                }
            }

            //finally, sort the notes and return the finished chart
            notes.Sort((a, b) => a.Time.Beats.CompareTo(b.Time.Beats));

            //TODO store difficulty in here
            var chart = new Chart("undefined", bpmGraph, notes);

            song.Charts = new List <Chart>();
            song.Charts.Add(chart);

            return(song);
        }