Ejemplo n.º 1
0
        private static void ProcessChannelNotes(Match match, int barOffset, BpmGraph barToBeat, BpmGraph bpmGraph,
                                                Dictionary <int, List <PartialData> > channelData)
        {
            int barNum  = ParseMmm(match.Groups[1]) + barOffset;
            int lane    = ParseXY(match.Groups[2]);
            int channel = ParseXY(match.Groups[3]);

            ProcessNotes(match.Groups[4], (barOff, type, width) =>
            {
                double beat = barToBeat.BeatAt(barNum + barOff);
                var time    = bpmGraph.FromBeat(beat);
                var pos     = new LanePosition(lane * 2, width * 2);
                if (!channelData.ContainsKey(channel))
                {
                    channelData.Add(channel, new List <PartialData>());
                }
                channelData[channel].Add(new PartialData(type, pos, time));
            });
        }
Ejemplo n.º 2
0
        private Chart ParseChart(string[] tagData, BpmGraph bpm)
        {
            //0 will be the note tag, skip
            string chartType      = tagData[1].Replace(" ", "");
            int    keysPerMeasure = 4; //currently hardcoded for dance-single
            string description    = tagData[2].Replace(" ", "");
            string difficulty     = tagData[3].Replace(" ", "");
            int    meter          = int.Parse(tagData[4].Replace(" ", ""));

            //grooveRadar data below; we're ignoring this for now
            //String grooves[] = tagData[5].replaceAll(" ", "").split(",");
            string[] measureLines = tagData[6].Replace(" ", "").Split(',');

            var notes = new List <RhythmEvent>();

            for (int i = 0; i != measureLines.Length; i++)
            {
                parseMeasure(i, measureLines[i], keysPerMeasure, notes, bpm);
            }

            return(new Chart(difficulty, bpm, notes));
        }
Ejemplo n.º 3
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);
        }
Ejemplo n.º 4
0
        private void parseState(double offset, string keys, List <RhythmEvent> notes, BpmGraph bpm)
        {
            int totalActions = keys.Count(key => "124LF".Contains(key + "")) + _activeHoldCount;

            int laneWidth = 32 / keys.Length;

            for (int i = 0; i < keys.Length; i++)
            {
                switch (keys[i])
                {
                case '0':     //none
                    break;

                case '1':     //normal
                    notes.Add(new SimpleNote(bpm.FromBeat(offset), new LanePosition(i * laneWidth, laneWidth),
                                             totalActions >= 2));
                    break;

                case 'M':     //mine
                    notes.Add(new Mine(bpm.FromBeat(offset), new LanePosition(i * laneWidth, laneWidth)));
                    break;

                case 'F':     //fake
                    //notes.Add(new Mine(bpm.FromBeat(offset), new LanePosition(i * laneWidth, laneWidth), true));
                    break;

                case '2':     //start hold
                case '4':     //start roll
                {
                    notes.Add(new SimpleNote(bpm.FromBeat(offset), new LanePosition(i * laneWidth, laneWidth), keys[i] == '4'));
                    var hold = new HoldNote(bpm.FromBeat(offset), new LanePosition(i * laneWidth, laneWidth),
                                            new SlidePoint[1]);
                    notes.Add(hold);
                    _activeHoldCount++;
                    _activeHolds[i * laneWidth] = hold;
                }
                break;

                case '3':     // end hold/roll
                {
                    var activeHold = _activeHolds[i * laneWidth];
                    activeHold.SlidePoints[0]   = new SlidePoint(activeHold, bpm.FromBeat(offset), activeHold.Position);
                    _activeHolds[i * laneWidth] = null;
                    _activeHoldCount--;
                }
                break;
                    //TODO holds, rolls, lifts, fakes etc.
                }
            }
        }
Ejemplo n.º 5
0
        private void parseMeasure(int beat, string keyData, int keysPerMeasure, List <RhythmEvent> notes, BpmGraph bpm)
        {
            if (keyData.Length % keysPerMeasure != 0)
            {
                throw new ArgumentException("incorrect number of keys in measure: " + keyData);
            }

            int states = keyData.Length / keysPerMeasure;

            for (int i = 0; i != states; i++)
            {
                double offset = (beat + (1.0 / states) * i) * 4;
                parseState(offset, keyData.Substring(i * keysPerMeasure, keysPerMeasure), notes, bpm);
            }
        }
Ejemplo n.º 6
0
        public Song ParseSong(string filename)
        {
            var theSourceFile = new FileInfo(filename);
            var reader        = theSourceFile.OpenText();

            var contentsBuffer = new StringBuilder();

            string line;

            do
            {
                line = reader.ReadLine();
                if (line != null)
                {
                    contentsBuffer.AppendLine(line);
                }
            } while (line != null);

            var contents = contentsBuffer.ToString();

            contents = Regex.Replace(contents, @"//.*.\n", "");
            contents = Regex.Replace(contents, @"\n|\r", "");
            contents = Regex.Replace(contents, @"\t", " ");
            contents = Regex.Replace(contents, @",#NOTES", ";#NOTES");

            var      song       = new Song();
            var      charts     = new List <Chart>();
            var      bpmChanges = new List <BeatEvent>();
            var      bpmStops   = new List <BeatEvent>();
            BpmGraph bpmGraph   = null;

            string title     = "";
            string subTitle  = "";
            string artist    = "";
            string musicFile = "";

            double offset = Double.NaN;

            foreach (var command in contents.Split(';'))
            {
                string[] bits = command.Split(':'); //0=command, 1=data (more for #NOTES)
                if (bits.Length == 1)               // blank given, enter blank in manually
                {
                    bits    = new string[2];
                    bits[0] = command.Replace(":", "");
                    bits[1] = "";
                }

                if (!bits[0].StartsWith("#"))
                {
                    //System.out.println("warning: \"" + command + "\" is not a tag");
                    continue;
                }

                bits[0] = bits[0].Substring(1).ToLower(); //remove # and make lowercase (now case insensitive)
                switch (bits[0])
                {
                case "title":
                    if (title == "")
                    {
                        title = bits[1];
                    }
                    break;

                case "subtitle":
                    if (subTitle == "")
                    {
                        subTitle = bits[1];
                    }
                    break;

                case "titletranslit":
                    if (bits[1].Trim() != "")
                    {
                        title = bits[1];
                    }
                    break;

                case "subtitletranslit":
                    if (bits[1].Trim() != "")
                    {
                        subTitle = bits[1];
                    }
                    break;

                case "artist":
                case "credit":
                    artist = bits[1];
                    break;

                case "music":
                    musicFile = bits[1];
                    break;

                case "offset":
                    offset = double.Parse(bits[1], CultureInfo.InvariantCulture);
                    break;

                case "notes":
                    if (bpmGraph == null)
                    {
                        bpmGraph = BpmGraph.ParseBpm(bpmChanges, bpmStops);
                    }

                    if (bits[1].Replace(" ", "") == "dance-single")
                    {
                        charts.Add(ParseChart(bits, bpmGraph));
                    }
                    else
                    {
                        //System.out.println("chart type " + bits[1] + ", ignoring");
                    }

                    break;

                case "bpms":
                {
                    string[] bpms = bits[1].Split(',');
                    foreach (var bpm in bpms)
                    {
                        if (bpm != "")
                        {
                            bpmChanges.Add(new BeatEvent(bpm));
                        }
                    }
                }
                break;

                case "stops":
                {
                    string[] stops = bits[1].Split(',');
                    if (bits[1] != "")
                    {
                        foreach (var stop in stops)
                        {
                            if (stop != "")
                            {
                                bpmStops.Add(new BeatEvent(stop));
                            }
                        }
                    }
                }
                break;

                default:
                    //System.out.println("Unknown tag \"" + bits[0] + "\", ignoring");
                    break;
                }
            }

            song.Name      = title;
            song.AudioFile = musicFile;
            song.Charts    = charts;
            song.Offset    = offset;

            return(song);
        }