/// <summary>
        /// Loads a MIDI file from the stream.
        /// </summary>
        /// <param name="stream">The data stream used to load the MIDI file.</param>
        /// <param name="loopType">The type of the loop extension to be used.</param>
        public MidiFile(Stream stream, MidiFileLoopType loopType)
        {
            if (stream == null)
            {
                throw new ArgumentNullException(nameof(stream));
            }

            Load(stream, 0, loopType);
        }
        /// <summary>
        /// Loads a MIDI file from the file.
        /// </summary>
        /// <param name="path">The MIDI file name and path.</param>
        /// <param name="loopType">The type of the loop extension to be used.</param>
        public MidiFile(string path, MidiFileLoopType loopType)
        {
            if (path == null)
            {
                throw new ArgumentNullException(nameof(path));
            }

            using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read))
            {
                Load(stream, 0, loopType);
            }
        }
            public static Message Common(byte status, byte data1, byte data2, MidiFileLoopType loopType)
            {
                byte channel = (byte)(status & 0x0F);
                byte command = (byte)(status & 0xF0);

                if (command == 0xB0)
                {
                    switch (loopType)
                    {
                    case MidiFileLoopType.RpgMaker:
                        if (data1 == 111)
                        {
                            return(LoopStart());
                        }
                        break;

                    case MidiFileLoopType.IncredibleMachine:
                        if (data1 == 110)
                        {
                            return(LoopStart());
                        }
                        if (data1 == 111)
                        {
                            return(LoopEnd());
                        }
                        break;

                    case MidiFileLoopType.FinalFantasy:
                        if (data1 == 116)
                        {
                            return(LoopStart());
                        }
                        if (data1 == 117)
                        {
                            return(LoopEnd());
                        }
                        break;
                    }
                }

                return(new Message(channel, command, data1, data2));
            }
        private static void ReadTrack(BinaryReader reader, MidiFileLoopType loopType, out List <Message> messages, out List <int> ticks)
        {
            var chunkType = reader.ReadFourCC();

            if (chunkType != "MTrk")
            {
                throw new InvalidDataException($"The chunk type must be 'MTrk', but was '{chunkType}'.");
            }

            reader.ReadInt32BigEndian();

            messages = new List <Message>();
            ticks    = new List <int>();

            int  tick       = 0;
            byte lastStatus = 0;

            while (true)
            {
                var delta = reader.ReadIntVariableLength();
                var first = reader.ReadByte();

                try
                {
                    tick = checked (tick + delta);
                }
                catch (OverflowException)
                {
                    throw new NotSupportedException("Long MIDI file is not supported.");
                }

                if ((first & 128) == 0)
                {
                    var command = lastStatus & 0xF0;
                    if (command == 0xC0 || command == 0xD0)
                    {
                        messages.Add(Message.Common(lastStatus, first));
                        ticks.Add(tick);
                    }
                    else
                    {
                        var data2 = reader.ReadByte();
                        messages.Add(Message.Common(lastStatus, first, data2, loopType));
                        ticks.Add(tick);
                    }

                    continue;
                }

                switch (first)
                {
                case 0xF0:     // System Exclusive
                    DiscardData(reader);
                    break;

                case 0xF7:     // System Exclusive
                    DiscardData(reader);
                    break;

                case 0xFF:     // Meta Event
                    switch (reader.ReadByte())
                    {
                    case 0x2F:         // End of Track
                        reader.ReadByte();
                        messages.Add(Message.EndOfTrack());
                        ticks.Add(tick);
                        return;

                    case 0x51:         // Tempo
                        messages.Add(Message.TempoChange(ReadTempo(reader)));
                        ticks.Add(tick);
                        break;

                    default:
                        DiscardData(reader);
                        break;
                    }
                    break;

                default:
                    var command = first & 0xF0;
                    if (command == 0xC0 || command == 0xD0)
                    {
                        var data1 = reader.ReadByte();
                        messages.Add(Message.Common(first, data1));
                        ticks.Add(tick);
                    }
                    else
                    {
                        var data1 = reader.ReadByte();
                        var data2 = reader.ReadByte();
                        messages.Add(Message.Common(first, data1, data2, loopType));
                        ticks.Add(tick);
                    }
                    break;
                }

                lastStatus = first;
            }
        }
        private void Load(Stream stream, int loopPoint, MidiFileLoopType loopType)
        {
            using (var reader = new BinaryReader(stream, Encoding.ASCII, true))
            {
                var chunkType = reader.ReadFourCC();
                if (chunkType != "MThd")
                {
                    throw new InvalidDataException($"The chunk type must be 'MThd', but was '{chunkType}'.");
                }

                var size = reader.ReadInt32BigEndian();
                if (size != 6)
                {
                    throw new InvalidDataException($"The MThd chunk has invalid data.");
                }

                var format = reader.ReadInt16BigEndian();
                if (!(format == 0 || format == 1))
                {
                    throw new NotSupportedException($"The format {format} is not supported.");
                }

                trackCount = reader.ReadInt16BigEndian();
                resolution = reader.ReadInt16BigEndian();

                var messageLists = new List <Message> [trackCount];
                var tickLists    = new List <int> [trackCount];
                for (var i = 0; i < trackCount; i++)
                {
                    List <Message> messageList;
                    List <int>     tickList;
                    ReadTrack(reader, loopType, out messageList, out tickList);
                    messageLists[i] = messageList;
                    tickLists[i]    = tickList;
                }

                if (loopPoint != 0)
                {
                    var tickList    = tickLists[0];
                    var messageList = messageLists[0];
                    if (loopPoint <= tickList.Last())
                    {
                        for (var i = 0; i < tickList.Count; i++)
                        {
                            if (tickList[i] >= loopPoint)
                            {
                                tickList.Insert(i, loopPoint);
                                messageList.Insert(i, Message.LoopStart());
                                break;
                            }
                        }
                    }
                    else
                    {
                        tickList.Add(loopPoint);
                        messageList.Add(Message.LoopStart());
                    }
                }

                MergeTracks(messageLists, tickLists, resolution, out messages, out times);
            }
        }