public void Add(int channelIndex, List <int> values) { BMSChannel channelValues = new BMSChannel(channelIndex); channelValues.values = values; channelList.Add(channelValues); }
public void Add(int channelIndex, int value) { BMSChannel channelValues = new BMSChannel(channelIndex); channelValues.values = new List <int>(1); channelValues.values.Add(value); channelList.Add(channelValues); }
// merges over values of zeroes with non-zero values from source channel public static void MergeChannels(BMSChannel destination, List <int> sourceList, bool lnWorkaround = false) { if (destination.values.Count != sourceList.Count) { int newLength = Utility.lcm(destination.values.Count, sourceList.Count); NormalizeChannel(destination, newLength, lnWorkaround ? -1 : 0); sourceList = NormalizeChannelValues(sourceList, newLength, lnWorkaround ? -1 : 0); } if (!lnWorkaround) { for (int i = 0; i < destination.values.Count; i++) { if (destination.values[i] == 0) { destination.values[i] = sourceList[i]; } } } else { // filler values are replaced with previous object values // so the consecutive repeating values are not broken in // final normalized list. int lastValue = 0, lastValue2 = 0; for (int i = 0; i < destination.values.Count; i++) { if (destination.values[i] <= 0) { destination.values[i] = sourceList[i]; if (destination.values[i] == -1) { destination.values[i] = lastValue2; } } if (destination.values[i] == -1) { destination.values[i] = lastValue; } lastValue = destination.values[i]; lastValue2 = sourceList[i]; } } }
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; } }
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; } }
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); }
// maps BMS player channels to lane numbers, first lane is scratch public static int GetLaneIndex(int channel, int keyCount, int playerCount) { int lane = -1; // long note if (channel >= (int)BMSChannel.Type.P1LongFirst && channel <= (int)BMSChannel.Type.P2LongLast) { channel -= (int)BMSChannel.Type.P1LongFirst - (int)BMSChannel.Type.P1KeyFirst; } // landmine if (channel >= (int)BMSChannel.Type.P1LandmineFirst && channel <= (int)BMSChannel.Type.P2LandmineLast) { channel -= (int)BMSChannel.Type.P1LandmineFirst - (int)BMSChannel.Type.P1KeyFirst; } int offset = 0; if (keyCount != 9) { if (playerCount == 2 && BMSChannel.IsP2Key(channel)) { offset = keyCount; channel -= BMSChannel.KeyBMS.P1Key1 - BMSChannel.KeyBMS.P2Key1; } if (BMSChannel.IsP1Key(channel)) { if (channel >= (int)BMSChannel.KeyBMS.P1Key1 && channel <= (int)BMSChannel.KeyBMS.P1Key5) { lane = 1 + channel - (int)BMSChannel.KeyBMS.P1Key1; } else if (channel >= (int)BMSChannel.KeyBMS.P1Key6 && channel <= (int)BMSChannel.KeyBMS.P1Key7) { lane = 1 + 5 + channel - (int)BMSChannel.KeyBMS.P1Key6; } else if (channel == (int)BMSChannel.KeyBMS.P1Scratch) { lane = 0; } } } else if (keyCount == 9) { if (playerCount == 1) { if (channel >= (int)BMSChannel.KeyPMS.P1Key1 && channel <= (int)BMSChannel.KeyPMS.P1Key5) { lane = channel - (int)BMSChannel.KeyPMS.P1Key1; } else if (channel >= (int)BMSChannel.KeyPMS.P1Key6 && channel <= (int)BMSChannel.KeyPMS.P1Key9) { lane = 5 + (channel - (int)BMSChannel.KeyPMS.P1Key6); } } else if (playerCount == 2) { if (channel >= (int)BMSChannel.KeyPMS.P2DPKey1) { offset = 9; channel -= BMSChannel.KeyPMS.P2DPKey1 - BMSChannel.KeyPMS.P1DPKey1; } if (channel >= (int)BMSChannel.KeyPMS.P1DPKey1 && channel <= (int)BMSChannel.KeyPMS.P1DPKey5) { lane = channel - (int)BMSChannel.KeyPMS.P1DPKey1; } } if (channel == (int)BMSChannel.KeyPMS.P1DPKey6) { lane = 5; } else if (channel == (int)BMSChannel.KeyPMS.P1DPKey7) { lane = 6; } else if (channel == (int)BMSChannel.KeyPMS.P1DPKey8) { lane = 7; } else if (channel == (int)BMSChannel.KeyPMS.P1DPKey9) { lane = 8; } } lane += offset; return(lane); }
public static void NormalizeChannel(BMSChannel channel, int maxLength, int fillerValue = 0) { channel.values = NormalizeChannelValues(channel.values, maxLength, fillerValue); }
public static void MergeChannels(BMSChannel destination, BMSChannel source, bool lnWorkaround = false) { MergeChannels(destination, source.values, lnWorkaround); }