private void LoadThread() { try { while (true) { SoundObject sound = null; BGAObject bga = null; if (!soundQueue.TryDequeue(out sound)) { bgaQueue.TryDequeue(out bga); } if (sound != null && !sound.loaded) { LoadSound(sound); } else if (bga != null && !bga.loaded) { LoadBGA(bga); } else if (!playing && soundQueue.Count == 0 && bgaQueue.Count == 0) { break; } } } catch (ThreadAbortException) { } }
private void LoadBGA(BGAObject bgaObject) { string filename = bgaObject.path; string fullPath = Path.Combine(basePath, filename); string path = Utility.FindRealFile(fullPath, lookupPaths, lookupImageExtensions); if (File.Exists(path)) { if (Path.GetExtension(filename).ToLower() == ".lua") { Log.Error("Failed to load BGA '" + filename + "', scripted BGAs are not supported"); return; } FFmpegVideo video = new FFmpegVideo(); try { video.Load(path); byte[] bytes = video.ReadFrame(); bgaObject.SetVideo(video); lock (rendererLock) video.OnNextFrame(bytes); } catch (ThreadAbortException) { } catch (Exception e) { Log.Error("Failed to load BGA '" + Path.GetFileName(filename) + "': " + e.Message); } finally { if (!video.isVideo) { video.Dispose(); } } } else if (!missingFiles.Contains(filename)) { missingFiles.Add(filename); Log.Warning("BGA file not found: " + filename); } }
public BGAEvent(long pulse, BGAObject bga, BGAType type = BGAType.BGA) : base(pulse) { this.bga = bga; this.type = type; }
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; } }
public override void GenerateEvents() { if (!(this.bmson is BMSON)) { throw new ApplicationException("Can not generate events from partial BMSON object"); } BMSON bmson = (BMSON)this.bmson; eventList = new List <Event>(); Version version; if (!Version.TryParse(bmson.version, out version)) { version = new Version(0, 0, 0, 0); } // some charts use judge_rank the same way as #RANK in BMS charts // which is wrong, as the judge_rank is supposed to be a multiplier in percents. if (bmson.info.judge_rank <= 4) { rankLegacy = (int)bmson.info.judge_rank; } else { rankMultiplierReal = bmson.info.judge_rank / 100.0; } total = bmson.info.total; // total value should be in percentages, but the value was entered // as a multiplier, convert the value back to percentages. if (total <= 13) // arbitrary value { total = bmson.info.total * 100.0; } // collect all time related events into one collection if (version.Major == 0) // bmson 0.21 { timeEventList = new List <Event>(bmson.bpmEvents.Length + bmson.stopEvents.Length); foreach (BMSON.BMSONEventNote bpmEvent in bmson.bpmEvents) { timeEventList.Add(new BPMEvent(bpmEvent.y, bpmEvent.v)); } // convert stop time to pulses double currentBpm = bmson.info.initBPM; int lastBpmEventIndex = 0; foreach (BMSON.BMSONEventNote stopEvent in bmson.stopEvents) { long pulse = stopEvent.y; for (; lastBpmEventIndex < bmson.bpmEvents.Length; lastBpmEventIndex++) { BMSON.BMSONEventNote bpmEvent = bmson.bpmEvents[lastBpmEventIndex]; if (bpmEvent.y >= pulse) { break; } currentBpm = bpmEvent.v; } long stopPulses = (long)(stopEvent.v * currentBpm / 60.0 * resolution); timeEventList.Add(new StopEvent(pulse, stopPulses)); } } else if (version.Major >= 1) // bmson 1.0.0+ { timeEventList = new List <Event>(bmson.bpm_events.Length + bmson.stop_events.Length); foreach (BMSON.BMSONBpmEvent bpmEvent in bmson.bpm_events) { timeEventList.Add(new BPMEvent(bpmEvent.y, bpmEvent.bpm)); } foreach (BMSON.BMSONStopEvent stopEvent in bmson.stop_events) { timeEventList.Add(new StopEvent(stopEvent.y, stopEvent.duration)); } } timeEventList.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)))); })); eventList.AddRange(timeEventList); // measure markers foreach (BMSON.BarLine line in bmson.lines ?? new BMSON.BarLine[0]) { long pulse = (long)line.y; measurePositions.Add(new Tuple <int, long>(measureCount++, pulse)); eventList.Add(new MeasureMarkerEvent(pulse)); } // parse mode hints players = 1; string modeHint = bmson.info.mode_hint; switch (modeHint) { case "beat-5k": hasTurntable = true; playerChannels = 6; break; case "beat-7k": hasTurntable = true; playerChannels = 8; break; case "beat-10k": hasTurntable = true; players = 2; playerChannels = 6; break; case "beat-14k": hasTurntable = true; players = 2; playerChannels = 8; break; case "popn-5k": playerChannels = 5; break; case "popn-9k": playerChannels = 9; break; default: int keys = 0; if (modeHint.StartsWithFastIgnoreCase("generic-") && int.TryParse( modeHint.Split(new string[] { "generic-", "keys", }, StringSplitOptions.None)[1], out keys)) { // generic-nkeys throw new ApplicationException("Unsupported BMSON mode_hint generic-nkeys"); } else { throw new ApplicationException("Unsupported BMSON mode_hint: " + modeHint); } } Dictionary <int, Dictionary <long, NoteEvent> > uniqueNotes = new Dictionary <int, Dictionary <long, NoteEvent> >(); foreach (BMSON.SoundChannel channel in bmson.sound_channels ?? new BMSON.SoundChannel[0]) { double lastTimeEventTime = 0.0; double lastBpm = bpm; double lastMeter = 1.0; long lastPulse = 0; int lastTimeEventIndex = 0; SoundFile soundFile = new SoundFile(Path.Combine(basePath, channel.name)); List <double> sliceTimestamps = new List <double>(); int[] noteSlice = new int[channel.notes.Length]; SoundObject[] slices; // TODO: sort notes? // generate timestamps for sound slices long lastSlicePulse = -1; for (int i = 0; i < channel.notes.Length; i++) { BMSON.Note note = channel.notes[i]; long notePulse = (long)note.y; if (notePulse != lastSlicePulse) { // calculate timestamp for each pulse for (; lastTimeEventIndex < timeEventList.Count; lastTimeEventIndex++) { Event timeEvent = timeEventList[lastTimeEventIndex]; if (timeEvent.pulse >= notePulse) { break; } double increment = (double)(timeEvent.pulse - lastPulse) / resolution * 60.0 / (lastBpm / lastMeter); lastTimeEventTime += increment; lastPulse = timeEvent.pulse; if (timeEvent is BPMEvent) { lastBpm = (timeEvent as BPMEvent).bpm; if (lastBpm < 0.0) { lastBpm = -lastBpm; } } else if (timeEvent is StopEvent) { double stopPulses = (timeEvent as StopEvent).stopTime; double stopTime = stopPulses / resolution * 60.0 / lastBpm; lastTimeEventTime += stopTime; } else if (timeEvent is MeterEvent) { lastMeter = (timeEvent as MeterEvent).meter; } } double timestamp = lastTimeEventTime + (double)(notePulse - lastPulse) / resolution * 60.0 / (lastBpm / lastMeter); sliceTimestamps.Add(timestamp); } lastSlicePulse = notePulse; noteSlice[i] = sliceTimestamps.Count - 1; } // create SoundObjects of sound slices slices = new SoundObject[sliceTimestamps.Count]; double offset = 0.0; for (int i = 0; i < sliceTimestamps.Count; i++) { double sliceStart = sliceTimestamps[i]; double sliceEnd = i + 1 < sliceTimestamps.Count ? sliceTimestamps[i + 1] : 0.0; double length = sliceEnd - sliceStart; if (!channel.notes[i].c) { offset = sliceStart; } sliceStart -= offset; if (length <= 0.0) { length = 0.0; } slices[i] = new SoundObject(soundFile, 1, sliceStart, sliceStart + length, channel.name); } // generate note events for (int i = 0; i < channel.notes.Length; i++) { BMSON.Note note = channel.notes[i]; SoundObject soundObject = slices[noteSlice[i]]; int lane = note.x; long pulse = note.y; long length = note.l; if (lane == 0) { SoundEvent soundEvent = new SoundEvent(pulse, soundObject); eventList.Add(soundEvent); } else { if (hasTurntable && lane == 8) { lane = 0; } else if (hasTurntable && lane == 16) { lane = playerChannels; } else if (lane > 8) { lane--; } Dictionary <long, NoteEvent> laneNotes; if (!uniqueNotes.TryGetValue(lane, out laneNotes)) { laneNotes = new Dictionary <long, NoteEvent>(); uniqueNotes[lane] = laneNotes; } NoteEvent uniqueNote; if (laneNotes.TryGetValue(pulse, out uniqueNote)) { LongNoteEvent lnStartEvent = uniqueNote as LongNoteEvent; LongNoteEndEvent lnEndEvent = laneNotes[pulse + length] as LongNoteEndEvent; if (length != 0 && lnStartEvent != null) { if (lnStartEvent.endNote != lnEndEvent) { Log.Warning("Layered long note event contains different end note"); } lnStartEvent.sounds.Add(soundObject); if (lnEndEvent != null) { lnEndEvent.sounds.Add(soundObject); } } else if (length == 0) { if (lnStartEvent != null || lnEndEvent != null) { Log.Warning("Layered regular note overlaps with long note"); } uniqueNote.sounds.Add(soundObject); } } else { playerEventCount++; noteCount++; if (length == 0) { NoteEvent noteEvent = new NoteEvent(pulse, soundObject, lane); eventList.Add(noteEvent); laneNotes[pulse] = noteEvent; } else { longNoteCount++; LongNoteEndEvent lnEndEvent = new LongNoteEndEvent(pulse + length, soundObject, lane, null); LongNoteEvent lnStartEvent = new LongNoteEvent(pulse, soundObject, lane, lnEndEvent); lnEndEvent.startNote = lnStartEvent; eventList.Add(lnStartEvent); eventList.Add(lnEndEvent); laneNotes[pulse] = lnStartEvent; if (!laneNotes.ContainsKey(pulse + length)) { laneNotes[pulse + length] = lnEndEvent; } else { NoteEvent overlappingNote = laneNotes[pulse + length]; if (overlappingNote is LongNoteEndEvent) { overlappingNote.sounds.Add(soundObject); } else { Log.Warning("Layered long note end point does not overlap with other long note end point"); } } } } } } } // populate BGA objects Dictionary <uint, BGAObject> bgaObjects = new Dictionary <uint, BGAObject>(bmson.bga.bga_header?.Length ?? 0); foreach (BMSON.BGAHeader bgaHeader in bmson.bga.bga_header ?? new BMSON.BGAHeader[0]) { bgaObjects[bgaHeader.id] = new BGAObject(bgaHeader.name, bgaHeader.name); } // generate BGA events foreach (BMSON.BGAEvent bga in bmson.bga.bga_events ?? new BMSON.BGAEvent[0]) { long pulse = bga.y; BGAObject bgaObject = null; if (bgaObjects.TryGetValue(bga.id, out bgaObject)) { eventList.Add(new BGAEvent(pulse, bgaObject, BGAEvent.BGAType.BGA)); } } foreach (BMSON.BGAEvent bga in bmson.bga.layer_events ?? new BMSON.BGAEvent[0]) { long pulse = bga.y; BGAObject bgaObject = null; if (bgaObjects.TryGetValue(bga.id, out bgaObject)) { eventList.Add(new BGAEvent(pulse, bgaObject, BGAEvent.BGAType.Layer)); } } foreach (BMSON.BGAEvent bga in bmson.bga.poor_events ?? new BMSON.BGAEvent[0]) { long pulse = bga.y; BGAObject bgaObject = null; if (bgaObjects.TryGetValue(bga.id, out bgaObject)) { eventList.Add(new BGAEvent(pulse, bgaObject, BGAEvent.BGAType.Poor)); } } eventList.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)))); })); GenerateTimestamps(); if (eventList.Count > 0) { songLength = eventList[eventList.Count - 1].timestamp; } }