Exemple #1
0
        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)
            {
            }
        }
Exemple #2
0
        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);
            }
        }
Exemple #3
0
 public BGAEvent(long pulse, BGAObject bga, BGAType type = BGAType.BGA)
     : base(pulse)
 {
     this.bga  = bga;
     this.type = type;
 }
Exemple #4
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;
            }
        }
Exemple #5
0
        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;
            }
        }