Exemplo n.º 1
0
        private void OnChannel(BMSChart data, string line, ref BMSMeasure lastMeasure)
        {
            // channel sentences
            // # <measure:3> <channel:2> : <object data>
            // for object data, 00 = musical rest

            if (headerOnly)
            {
                return;
            }

            int    measureIndex;
            int    channelIndex;
            string channelValue;

            if (!ParseChannelLine(line, out measureIndex, out channelIndex, out channelValue))
            {
                Log.Error("Invalid channel line");
                return;
            }

            if (channelRefs.ContainsKey(channelIndex))
            {
                channelRefs[channelIndex]++;
            }
            else
            {
                channelRefs.Add(channelIndex, 1);
            }

            if (!BMSChannel.IsSupported(channelIndex))
            {
                Log.Error("Unsupported channel type " + line.Substring(4, 2));
                return;
            }

            bool isSoundChannel  = BMSChannel.IsSound(channelIndex);
            bool isPlayerChannel = BMSChannel.IsPlayer(channelIndex);

            BMSMeasure measure = null;

            if (lastMeasure != null && lastMeasure.index == measureIndex)
            {
                measure = lastMeasure;
            }
            else
            {
                measure = data.GetMeasure(measureIndex);
                if (measure == null)
                {
                    measure = new BMSMeasure(measureIndex);
                    data.measureList.Add(measure);
                }
            }
            lastMeasure = measure;

            int channelLength = 1;

            if (channelIndex == (int)BMSChannel.Type.Meter)
            {
                double meter = 1.0;
                if (!double.TryParse(channelValue,
                                     NumberStyles.Float, CultureInfo.InvariantCulture, out meter))
                {
                    channelValue = channelValue.Replace(",", ".");
                    if (!double.TryParse(channelValue,
                                         NumberStyles.Float, CultureInfo.InvariantCulture, out meter))
                    {
                        Log.Error("Unable to parse meter value: " + channelValue.ToString());
                    }
                }
                measure.meter = meter;
            }
            else
            {
                channelLength = channelValue.Length / 2;
                if (channelValue.Length % 2 != 0)
                {
                    Log.Warning("Channel data length not divisible by 2");
                }

                List <int> objects = new List <int>(channelLength);
                for (int i = 0; i < channelValue.Length; i += 2)
                {
                    int value;
                    if (channelIndex == (int)BMSChannel.Type.BPM)
                    {
                        string bpmStr = channelValue.Substring(i, 2);
                        if (!int.TryParse(bpmStr, NumberStyles.HexNumber,
                                          CultureInfo.InvariantCulture, out value))
                        {
                            Log.Warning("Invalid BPM value in channel line: " + bpmStr);
                            channelLength = objects.Count;
                            break;
                        }
                    }
                    else
                    {
                        if (!Utility.TryFromBase36(channelValue[i], channelValue[i + 1], out value))
                        {
                            Log.Warning("Invalid value in channel line: " + channelValue.Substring(i, 2));
                            channelLength = objects.Count;
                            break;
                        }
                    }

                    objects.Add(value);

                    if (value != 0)
                    {
                        data.eventCount++;
                    }
                }

                // new channel data is applied to existing channel data (excluding BGM channel)
                if (channelIndex != (int)BMSChannel.Type.BGM && !BMSChannel.IsLong(channelIndex) &&
                    measure.HasChannel(channelIndex))
                {
                    int longNoteType = data.GetHeader <int>("LNTYPE");

                    foreach (BMSChannel channel in measure.channelList)
                    {
                        if (channel.index != channelIndex)
                        {
                            continue;
                        }

                        // when merging data with LNTYPE 2, section filler must not be
                        // filled with 0's in order to not close the long notes at wrong times.
                        bool lnWorkaround = longNoteType == 2 && BMSChannel.IsLong(channel.index);
                        BMSChannel.MergeChannels(channel, objects, lnWorkaround);

                        channelLength = channel.values.Count;
                        break;
                    }
                }
                else if (objects.Count > 0)
                {
                    measure.Add(channelIndex, objects);
                }
            }

            if (channelLength > measure.maxLength)
            {
                measure.maxLength = channelLength;
            }
        }
Exemplo n.º 2
0
        public override void GenerateEvents()
        {
            eventList = new List <Event>(eventCount);

            int longNoteType = GetHeader <int>("LNTYPE");
            Dictionary <int, LongNoteEvent> lastLNEvent     = new Dictionary <int, LongNoteEvent>();
            Dictionary <int, LongNoteEvent> startLNEvent    = new Dictionary <int, LongNoteEvent>();
            Dictionary <int, NoteEvent>     lastPlayerEvent = new Dictionary <int, NoteEvent>();

            long   pulse      = 0;
            double currentBpm = bpmObjects[0];
            double meter      = 1.0;

            if (longNoteType == 2)
            {
                // insert empty measure at the end to close longnotes ending at last beat (#LNTYPE 2)

                int measureIndex = 0;
                if (measureList.Count > 0)
                {
                    measureIndex = measureList[measureList.Count - 1].index + 1;
                }

                BMSMeasure measure = new BMSMeasure(measureIndex);
                measureList.Add(measure);
            }

            foreach (BMSMeasure measure in measureList)
            {
                List <Event> measureEvents = new List <Event>();

                // check if long notes should be ended at previous measure (#LNTYPE 2)
                if (longNoteType == 2 && lastLNEvent.Count > 0)
                {
                    List <int> keys = lastLNEvent.Keys.ToList();
                    foreach (var lnChannel in keys)
                    {
                        bool breakNote = true;
                        for (int i = 0; i < measure.channelList.Count; i++)
                        {
                            if (measure.channelList[i].index != lnChannel)
                            {
                                continue;
                            }
                            else if (measure.channelList[i].values.Count > 0 &&
                                     measure.channelList[i].values[0] != 0)
                            {
                                breakNote = false;
                                break;
                            }
                        }

                        if (breakNote)
                        {
                            // LN section ended at the last note in previous measure,
                            // and does not continue in next measure, so restore the
                            // last skipped event, and end the long note there.

                            LongNoteEvent    lastEvent  = lastLNEvent[lnChannel];
                            LongNoteEvent    startEvent = startLNEvent[lnChannel];
                            LongNoteEndEvent endEvent   = new LongNoteEndEvent(pulse, lastEvent.sound, lastEvent.lane, startEvent);
                            startEvent.endNote = endEvent;

                            eventList.Add(endEvent);

                            lastLNEvent.Remove(lnChannel);
                            startLNEvent.Remove(lnChannel);
                        }
                    }
                }

                // reset meter back to 1.0
                if (meter != measure.meter)
                {
                    MeterEvent measureEvent = new MeterEvent(pulse, measure.meter);
                    eventList.Add(measureEvent);
                    timeEventList.Add(measureEvent);
                }
                meter = measure.meter;

                // mark measure position
                measureCount++;
                measurePositions.Add(new Tuple <int, long>(measure.index, pulse));
                MeasureMarkerEvent measureMarker = new MeasureMarkerEvent(pulse);
                eventList.Add(measureMarker);

                double nextBpm = 0.0;
                Dictionary <long, double> bpmExValues = new Dictionary <long, double>();
                foreach (BMSChannel channel in measure.channelList)
                {
                    long channelPulse  = pulse;
                    long beatPulse     = (resolution * 4) / channel.values.Count;
                    int  lane          = BMSChannel.GetLaneIndex(channel.index, playerChannels, players);
                    bool isLongChannel = BMSChannel.IsLong(channel.index);

                    for (int i = 0; i < channel.values.Count; i++, channelPulse += beatPulse)
                    {
                        int value = channel.values[i];
                        if (value == 0 && !isLongChannel)
                        {
                            continue;
                        }

                        Event bmsEvent = null;
                        if (BMSChannel.IsSound(channel.index))
                        {
                            SoundObject sound = null;
                            soundObjects.TryGetValue(value, out sound);

                            if (channel.index == (int)BMSChannel.Type.BGM)
                            {
                                bmsEvent = new SoundEvent(channelPulse, sound);
                            }
                            else if (BMSChannel.IsInvisible(channel.index))
                            {
                                bmsEvent = new KeySoundChangeEvent(channelPulse, sound, lane);
                            }
                            else if (BMSChannel.IsLandmine(channel.index))
                            {
                                // landmine does damage based on the object value itself,
                                // and plays the hit sound from WAV00.

                                int damage = value;
                                soundObjects.TryGetValue(0, out sound);
                                bmsEvent = new LandmineEvent(channelPulse, sound, lane, damage);
                            }
                            else
                            {
                                NoteEvent noteEvent = null;
                                bool      isLnObj   = lnObjects.Contains(value);
                                if (isLongChannel || isLnObj)
                                {
                                    LongNoteEvent longNoteEvent = new LongNoteEvent(channelPulse, sound, lane, null);
                                    if (longNoteType == 2)
                                    {
                                        // ignore section filler events with #LNTYPE 2
                                        // skip all the events except the first one, and restore the last
                                        // skipped event at the end of long note.

                                        if (lastLNEvent.ContainsKey(channel.index))
                                        {
                                            if (value != 0)
                                            {
                                                lastLNEvent[channel.index] = longNoteEvent;
                                                continue;
                                            }
                                            else
                                            {
                                                // long note breaks here,
                                                // replace current event with last skipped LN event.

                                                LongNoteEvent    startEvent = startLNEvent[channel.index];
                                                LongNoteEndEvent endEvent   = new LongNoteEndEvent(channelPulse, sound, lane, startEvent);
                                                startEvent.endNote = endEvent;

                                                lastLNEvent.Remove(channel.index);
                                                startLNEvent.Remove(channel.index);

                                                noteEvent = endEvent;
                                            }
                                        }
                                        else if (value != 0)
                                        {
                                            lastLNEvent.Add(channel.index, longNoteEvent);
                                            startLNEvent.Add(channel.index, longNoteEvent);
                                            noteEvent = longNoteEvent;
                                        }
                                    }
                                    else if (longNoteType == 1 && value != 0)
                                    {
                                        LongNoteEvent lastLongNote = null;

                                        if (isLnObj)
                                        {
                                            NoteEvent lastEvent = lastPlayerEvent[lane];
                                            if (lastEvent != null)
                                            {
                                                lastPlayerEvent[lane] = null;
                                                lastLongNote          = new LongNoteEvent(lastEvent.pulse, lastEvent.sound, lastEvent.lane, null);

                                                bool foundNote = false;
                                                for (int j = measureEvents.Count - 1; j >= 0; j--)
                                                {
                                                    if (measureEvents[j] != lastEvent)
                                                    {
                                                        continue;
                                                    }

                                                    measureEvents[j] = lastLongNote;
                                                    foundNote        = true;
                                                    break;
                                                }
                                                if (!foundNote)
                                                {
                                                    for (int j = eventList.Count - 1; j >= 0; j--)
                                                    {
                                                        if (eventList[j] != lastEvent)
                                                        {
                                                            continue;
                                                        }

                                                        eventList[j] = lastLongNote;
                                                        foundNote    = true;
                                                        break;
                                                    }
                                                }

                                                if (!foundNote)
                                                {
                                                    throw new ApplicationException("Could not find long note starting point");
                                                }
                                            }
                                            else
                                            {
                                                noteEvent = new NoteEvent(channelPulse, sound, lane);
                                            }
                                        }
                                        else
                                        {
                                            lastLNEvent.TryGetValue(channel.index, out lastLongNote);
                                        }

                                        if (lastLongNote != null)
                                        {
                                            LongNoteEvent    startEvent = lastLongNote;
                                            LongNoteEndEvent endEvent   = new LongNoteEndEvent(channelPulse, sound, lane, startEvent);
                                            startEvent.endNote = endEvent;

                                            lastLNEvent.Remove(channel.index);

                                            SoundObject releaseSound = null;
                                            if (startEvent.sound != sound && sound != null)
                                            {
                                                // play sound on release
                                                releaseSound = sound;
                                            }

                                            noteEvent = endEvent;
                                        }
                                        else if (isLongChannel)
                                        {
                                            lastLNEvent.Add(channel.index, longNoteEvent);
                                            noteEvent = longNoteEvent;
                                        }
                                    }
                                }
                                else
                                {
                                    noteEvent             = new NoteEvent(channelPulse, sound, lane);
                                    lastPlayerEvent[lane] = noteEvent;
                                }

                                bmsEvent = noteEvent;
                            }
                        }
                        else if (channel.index == (int)BMSChannel.Type.BPM)
                        {
                            bmsEvent = new BPMEvent(channelPulse, value);
                            nextBpm  = value;
                        }
                        else if (channel.index == (int)BMSChannel.Type.BPMExtended)
                        {
                            double bpmValue = 0.0;
                            if (bpmObjects.TryGetValue(value, out bpmValue) && bpmValue != 0.0)
                            {
                                bmsEvent = new BPMEvent(channelPulse, bpmValue);
                                nextBpm  = bpmValue;
                                bpmExValues.Add(channelPulse, bpmValue);
                            }
                        }
                        else if (channel.index == (int)BMSChannel.Type.Stop)
                        {
                            double stopValue = 0;
                            stopObjects.TryGetValue(value, out stopValue);

                            long stopTime = (long)(stopValue / 192.0 * resolution * 4);
                            bmsEvent = new StopEvent(channelPulse, stopTime);
                        }
                        else if (channel.index == (int)BMSChannel.Type.BGA ||
                                 channel.index == (int)BMSChannel.Type.BGALayer ||
                                 channel.index == (int)BMSChannel.Type.BGAPoor)
                        {
                            BGAObject bga = null;
                            bgaObjects.TryGetValue(value, out bga);

                            BGAEvent.BGAType type = BGAEvent.BGAType.BGA;
                            if (channel.index == (int)BMSChannel.Type.BGALayer)
                            {
                                type = BGAEvent.BGAType.LayerTransparentBlack;
                            }
                            else if (channel.index == (int)BMSChannel.Type.BGAPoor)
                            {
                                type = BGAEvent.BGAType.Poor;
                            }

                            bmsEvent = new BGAEvent(channelPulse, bga, type);
                        }
                        else
                        {
                            Log.Warning("Unsupported BMS channel: " + channel.index.ToString("X2"));
                        }

                        if (bmsEvent == null)
                        {
                            continue;
                        }

                        measureEvents.Add(bmsEvent);
                    }
                }

                pulse += resolution * 4;

                measureEvents.Sort(new Comparison <Event>((e1, e2) =>
                {
                    return(e1.pulse > e2.pulse ? 1 : (e1.pulse < e2.pulse ? -1 :
                                                      e1 is StopEvent ? 1 : (e2 is StopEvent ? -1 :
                                                                             e1 is BPMEvent ? 1 : (e2 is BPMEvent ? -1 : 0))));
                }));

                foreach (Event bmsEvent in measureEvents)
                {
                    NoteEvent noteEvent = bmsEvent as NoteEvent;
                    if (noteEvent != null)
                    {
                        playerEventCount++;

                        if (noteEvent is LandmineEvent)
                        {
                            landmineCount++;
                        }
                        else
                        {
                            noteCount++;
                            if (noteEvent is LongNoteEvent)
                            {
                                longNoteCount++;
                            }
                        }
                    }
                    else if (bmsEvent is BPMEvent)
                    {
                        // on overlap, prefer extended BPM (xxx08) changes over basic (xxx03) values
                        double otherBpm;
                        if (bpmExValues.TryGetValue(bmsEvent.pulse, out otherBpm))
                        {
                            if (otherBpm != (bmsEvent as BPMEvent).bpm)
                            {
                                continue;
                            }
                        }

                        timeEventList.Add(bmsEvent);
                    }
                    else if (bmsEvent is StopEvent)
                    {
                        timeEventList.Add(bmsEvent);
                    }

                    eventList.Add(bmsEvent);
                }
            }

            GenerateTimestamps();

            if (eventList.Count > 0)
            {
                songLength = eventList[eventList.Count - 1].timestamp;
            }
        }
Exemplo n.º 3
0
        public override Chart Load(string path)
        {
            BMSChart   data        = new BMSChart(Directory.GetParent(path).FullName);
            BMSMeasure lastMeasure = null;

            bool warnRandom = false;

            using (FileStream fileStream = new FileStream(path, FileMode.Open,
                                                          FileAccess.Read, FileShare.Read, 8192, FileOptions.SequentialScan))
            {
                using (StreamReader stream = new StreamReader(fileStream, System.Text.Encoding.GetEncoding("shift_jis"), true))
                {
                    string line;
                    int    lineNumber = -1;
                    while ((line = stream.ReadLine()) != null)
                    {
                        lineNumber++;

                        string command, value;
                        if (!ParseCommandLine(line, out command, out value))
                        {
                            continue;
                        }

                        if (command.StartsWithFast("WAV"))
                        {
                            OnWAV(data, command, value);
                        }
                        else if (command.StartsWithFast("BMP"))
                        {
                            OnBMP(data, command, value);
                        }
                        else if (command.StartsWithFast("BPM") || command.StartsWithFast("EXBPM"))
                        {
                            OnBPM(data, command, value);
                        }
                        else if (command.StartsWithFast("STOP"))
                        {
                            OnStop(data, command, value);
                        }
                        else if (command.StartsWithFast("LNOBJ"))
                        {
                            OnLongNote(data, command, value);
                        }
                        else if (command == "RANDOM")
                        {
                            warnRandom = true;
                        }
                        else if (command[0] >= '0' && command[0] <= '9')
                        {
                            OnChannel(data, line, ref lastMeasure);
                        }
                        else if (BMSChart.unsupportedObjects.Contains(command))
                        {
                            continue;                             // unsupported command, silently ignored
                        }
                        else
                        {
                            OnHeader(data, command, value);
                        }
                    }
                }
            }

            if (warnRandom)
            {
                Log.Warning("#RANDOM charts are not supported");
            }

            data.measureList.Sort(new Comparison <BMSMeasure>((m1, m2) =>
            {
                return(m1.index > m2.index ? 1 : (m1.index < m2.index ? -1 : 0));
            }));

            // insert empty measures at gaps
            for (int i = 0, skipped = 0; i < data.measureList.Count; i++)
            {
                if (data.measureList[i].index == i + skipped)
                {
                    continue;
                }

                if (skipToFirstMeasure && skipped == 0)
                {
                    skipped = data.measureList[i].index;
                    continue;
                }

                BMSMeasure measure = new BMSMeasure(i + skipped);
                BMSChannel channel = new BMSChannel(1);

                channel.values.Add(0);
                measure.channelList.Add(channel);
                measure.maxLength = 1;

                data.measureList.Insert(i, measure);
            }

            // check against overlapping long and regular notes
            int longNoteType = data.GetHeader <int>("LNTYPE");

            for (int i = 0; i < data.measureList.Count; i++)
            {
                for (int c = (int)BMSChannel.Type.P1KeyFirst; c < (int)BMSChannel.Type.P2KeyLast; ++c)
                {
                    BMSChannel keyChannel = data.measureList[i].GetChannel(c);
                    if (keyChannel == null)
                    {
                        continue;
                    }

                    for (int cl = 0; cl < data.measureList[i].channelList.Count; cl++)
                    {
                        if (data.measureList[i].channelList[cl].index != c + (BMSChannel.Type.P1LongFirst - BMSChannel.Type.P1KeyFirst))
                        {
                            continue;
                        }

                        BMSChannel lnChannel = data.measureList[i].channelList[cl];
                        if (keyChannel.values.Count != lnChannel.values.Count)
                        {
                            int  newLength    = Utility.lcm(keyChannel.values.Count, lnChannel.values.Count);
                            bool lnWorkaround = longNoteType == 2;
                            BMSChannel.NormalizeChannel(keyChannel, newLength, 0);
                            BMSChannel.NormalizeChannel(lnChannel, newLength, lnWorkaround ? -1 : 0);
                        }

                        int lnValue = -1;
                        for (int j = 0; j < keyChannel.values.Count; j++)
                        {
                            if (lnChannel.values[j] != -1)
                            {
                                lnValue = lnChannel.values[j];
                            }
                            else
                            {
                                lnChannel.values[j] = lnValue;
                            }

                            if (keyChannel.values[j] == 0 || lnValue == 0)
                            {
                                continue;
                            }
                            else if (lnValue != 0)
                            {
                                // Both channels have overlapping notes.
                                // Charter probably added both for compatibility reasons
                                // if the client doesn't support long notes.

                                keyChannel.values[j] = 0;
                            }
                        }
                    }
                }
            }

            if (longNoteType != 1)
            {
                Log.Warning("#LNTYPE " + longNoteType.ToString());
            }

            int p1KeyCount = 0;
            int p2KeyCount = 0;

            // merge long note references with regular keys
            List <int> channelKeys = channelRefs.Keys.ToList();

            foreach (int channelIndex in channelKeys)
            {
                if (BMSChannel.IsP1Long(channelIndex) || BMSChannel.IsP2Long(channelIndex))
                {
                    int offset     = BMSChannel.Type.P1LongFirst - BMSChannel.Type.P1KeyFirst;
                    int newChannel = channelIndex - offset;
                    int refs       = channelRefs[channelIndex];

                    if (channelRefs.ContainsKey(newChannel))
                    {
                        channelRefs[newChannel] += refs;
                    }
                    else
                    {
                        channelRefs.Add(newChannel, refs);
                    }
                }
            }

            foreach (var channel in channelRefs)
            {
                if (BMSChannel.IsP1Key(channel.Key))
                {
                    p1KeyCount++;
                }
                else if (BMSChannel.IsP2Key(channel.Key))
                {
                    p2KeyCount++;
                }
            }

            data.playerChannels = Math.Max(p1KeyCount, p2KeyCount);
            if (data.playerChannels != 6 && data.playerChannels != 8 && data.playerChannels != 9)
            {
                // non-standard format or some channels were left empty,
                // fallback to using specific key count based on the file extension.
                int    keyCount  = 0;
                string extension = Path.GetExtension(path).ToLowerInvariant();
                if (extension == ".bms")
                {
                    keyCount = 6;
                }
                else if (extension == ".bme" || extension == ".bml")
                {
                    keyCount = 8;
                }
                else if (extension == ".pms")
                {
                    keyCount = 9;

                    if (p1KeyCount <= 5 && p2KeyCount <= 4)
                    {
                        // actually one player chart
                        p2KeyCount = 0;
                    }
                }

                if (keyCount != 0)
                {
                    if (p1KeyCount > 0)
                    {
                        p1KeyCount = keyCount;
                    }
                    if (p2KeyCount > 0)
                    {
                        p2KeyCount = keyCount;
                    }
                }
            }

            data.playerChannels = Math.Max(p1KeyCount, p2KeyCount);
            data.hasTurntable   = data.playerChannels == 6 || data.playerChannels == 8;

            if (p2KeyCount > 0)
            {
                data.players = 2;
            }
            else if (p1KeyCount > 0)
            {
                data.players = 1;
            }

            // find the most optimal resolution for this chart
            long       resolution    = 1;
            const long maxResolution = long.MaxValue / (1000 * 4);

            try
            {
                foreach (BMSMeasure measure in data.measureList)
                {
                    foreach (BMSChannel channel in measure.channelList)
                    {
                        if (resolution % channel.values.Count != 0)
                        {
                            resolution = Utility.lcm(resolution, channel.values.Count);
                        }
                    }
                }
            }
            catch (ArithmeticException)
            {
                resolution = 0;
            }
            finally
            {
                if (resolution <= 0 || resolution > maxResolution)
                {
                    resolution = maxResolution;
                    Log.Warning("Required object resolution is too high for accurate playback");
                }
            }

            data.resolution_ = resolution;

            return(data);
        }