private MidiFile(Stream inputStream, bool strictChecking, bool ownInputStream) { this.strictChecking = strictChecking; var br = new BinaryReader(inputStream); try { string chunkHeader = Encoding.UTF8.GetString(br.ReadBytes(4)); if (chunkHeader != "MThd") { throw new FormatException("Not a MIDI file - header chunk missing"); } uint chunkSize = SwapUInt32(br.ReadUInt32()); if (chunkSize != 6) { throw new FormatException("Unexpected header chunk length"); } // 0 = single track, 1 = multi-track synchronous, 2 = multi-track asynchronous fileFormat = SwapUInt16(br.ReadUInt16()); int tracks = SwapUInt16(br.ReadUInt16()); deltaTicksPerQuarterNote = SwapUInt16(br.ReadUInt16()); events = new MidiEventCollection((fileFormat == 0) ? 0 : 1, deltaTicksPerQuarterNote); for (int n = 0; n < tracks; n++) { events.AddTrack(); } long absoluteTime = 0; for (int track = 0; track < tracks; track++) { if (fileFormat == 1) { absoluteTime = 0; } chunkHeader = Encoding.UTF8.GetString(br.ReadBytes(4)); if (chunkHeader != "MTrk") { throw new FormatException("Invalid chunk header"); } chunkSize = SwapUInt32(br.ReadUInt32()); long startPos = br.BaseStream.Position; MidiEvent me = null; var outstandingNoteOns = new List <NoteOnEvent>(); while (br.BaseStream.Position < startPos + chunkSize) { try { me = MidiEvent.ReadNextEvent(br, me); } catch (InvalidDataException) { if (strictChecking) { throw; } continue; } catch (FormatException) { if (strictChecking) { throw; } continue; } absoluteTime += me.DeltaTime; me.AbsoluteTime = absoluteTime; events[track].Add(me); if (me.CommandCode == MidiCommandCode.NoteOn) { var ne = (NoteEvent)me; if (ne.Velocity > 0) { outstandingNoteOns.Add((NoteOnEvent)ne); } else { // don't remove the note offs, even though // they are annoying // events[track].Remove(me); FindNoteOn(ne, outstandingNoteOns); } } else if (me.CommandCode == MidiCommandCode.NoteOff) { FindNoteOn((NoteEvent)me, outstandingNoteOns); } else if (me.CommandCode == MidiCommandCode.MetaEvent) { MetaEvent metaEvent = (MetaEvent)me; if (metaEvent.MetaEventType == MetaEventType.EndTrack) { //break; // some dodgy MIDI files have an event after end track if (strictChecking) { if (br.BaseStream.Position < startPos + chunkSize) { throw new FormatException( $"End Track event was not the last MIDI event on track {track}"); } } } } } if (outstandingNoteOns.Count > 0) { if (strictChecking) { throw new FormatException( $"Note ons without note offs {outstandingNoteOns.Count} (file format {fileFormat})"); } } if (br.BaseStream.Position != startPos + chunkSize) { throw new FormatException($"Read too far {chunkSize}+{startPos}!={br.BaseStream.Position}"); } } } finally { if (ownInputStream) { #if NET35 br.Close(); #else br.Dispose(); #endif } } }
/// <summary> /// Opens a MIDI file for reading /// </summary> /// <param name="filename">Name of MIDI file</param> /// <param name="strictChecking">If true will error on non-paired note events</param> public MidiFile(string filename, bool strictChecking) { this.strictChecking = strictChecking; BinaryReader br = new BinaryReader(File.OpenRead(filename)); using (br) { string chunkHeader = Encoding.ASCII.GetString(br.ReadBytes(4)); if (chunkHeader != "MThd") { throw new FormatException("Not a MIDI file - header chunk missing"); } uint chunkSize = SwapUInt32(br.ReadUInt32()); if (chunkSize != 6) { throw new FormatException("Unexpected header chunk length"); } // 0 = single track, 1 = multi-track synchronous, 2 = multi-track asynchronous fileFormat = SwapUInt16(br.ReadUInt16()); int tracks = SwapUInt16(br.ReadUInt16()); deltaTicksPerQuarterNote = SwapUInt16(br.ReadUInt16()); events = new MidiEventCollection((fileFormat == 0) ? 0 : 1, deltaTicksPerQuarterNote); for (int n = 0; n < tracks; n++) { events.AddTrack(); } long absoluteTime = 0; for (int track = 0; track < tracks; track++) { if (fileFormat == 1) { absoluteTime = 0; } chunkHeader = Encoding.ASCII.GetString(br.ReadBytes(4)); if (chunkHeader != "MTrk") { throw new FormatException("Invalid chunk header"); } chunkSize = SwapUInt32(br.ReadUInt32()); long startPos = br.BaseStream.Position; MidiEvent me = null; List <NoteOnEvent> outstandingNoteOns = new List <NoteOnEvent>(); while (br.BaseStream.Position < startPos + chunkSize) { me = MidiEvent.ReadNextEvent(br, me); absoluteTime += me.DeltaTime; me.AbsoluteTime = absoluteTime; events[track].Add(me); if (me.CommandCode == MidiCommandCode.NoteOn) { NoteEvent ne = (NoteEvent)me; if (ne.Velocity > 0) { outstandingNoteOns.Add((NoteOnEvent)ne); } else { // don't remove the note offs, even though // they are annoying // events[track].Remove(me); FindNoteOn(ne, outstandingNoteOns); } } else if (me.CommandCode == MidiCommandCode.NoteOff) { FindNoteOn((NoteEvent)me, outstandingNoteOns); } else if (me.CommandCode == MidiCommandCode.MetaEvent) { MetaEvent metaEvent = (MetaEvent)me; if (metaEvent.MetaEventType == MetaEventType.EndTrack) { //break; // some dodgy MIDI files have an event after end track if (strictChecking) { if (br.BaseStream.Position < startPos + chunkSize) { throw new FormatException(String.Format("End Track event was not the last MIDI event on track {0}", track)); } } } } } if (outstandingNoteOns.Count > 0) { if (strictChecking) { throw new FormatException(String.Format("Note ons without note offs {0} (file format {1})", outstandingNoteOns.Count, fileFormat)); } } if (br.BaseStream.Position != startPos + chunkSize) { throw new FormatException(String.Format("Read too far {0}+{1}!={2}", chunkSize, startPos, br.BaseStream.Position)); } } } }