/** Parse a single Midi track into a list of MidiEvents. * Entering this function, the file offset should be at the start of * the MTrk header. Upon exiting, the file offset should be at the * start of the next MTrk header. */ private List<MidiEvent> ReadTrack(MidiFileReader file) { List<MidiEvent> result = new List<MidiEvent>(20); int starttime = 0; string id = file.ReadAscii(4); if (id != "MTrk") { throw new MidiFileException("Bad MTrk header", file.GetOffset() - 4); } int tracklen = file.ReadInt(); int trackend = tracklen + file.GetOffset(); int eventflag = 0; while (file.GetOffset() < trackend) { // If the midi file is truncated here, we can still recover. // Just return what we've parsed so far. int startoffset, deltatime; byte peekevent; try { startoffset = file.GetOffset(); deltatime = file.ReadVarlen(); starttime += deltatime; peekevent = file.Peek(); } catch (MidiFileException e) { return result; } MidiEvent mevent = new MidiEvent(); result.Add(mevent); mevent.DeltaTime = deltatime; mevent.StartTime = starttime; if (peekevent >= EventNoteOff) { mevent.HasEventflag = true; eventflag = file.ReadByte(); } // Console.WriteLine("offset {0}: event {1} {2} start {3} delta {4}", // startoffset, eventflag, EventName(eventflag), // starttime, mevent.DeltaTime); if (eventflag >= EventNoteOn && eventflag < EventNoteOn + 16) { mevent.EventFlag = EventNoteOn; mevent.Channel = (byte)(eventflag - EventNoteOn); mevent.Notenumber = file.ReadByte(); mevent.Velocity = file.ReadByte(); } else if (eventflag >= EventNoteOff && eventflag < EventNoteOff + 16) { mevent.EventFlag = EventNoteOff; mevent.Channel = (byte)(eventflag - EventNoteOff); mevent.Notenumber = file.ReadByte(); mevent.Velocity = file.ReadByte(); } else if (eventflag >= EventKeyPressure && eventflag < EventKeyPressure + 16) { mevent.EventFlag = EventKeyPressure; mevent.Channel = (byte)(eventflag - EventKeyPressure); mevent.Notenumber = file.ReadByte(); mevent.KeyPressure = file.ReadByte(); } else if (eventflag >= EventControlChange && eventflag < EventControlChange + 16) { mevent.EventFlag = EventControlChange; mevent.Channel = (byte)(eventflag - EventControlChange); mevent.ControlNum = file.ReadByte(); mevent.ControlValue = file.ReadByte(); } else if (eventflag >= EventProgramChange && eventflag < EventProgramChange + 16) { mevent.EventFlag = EventProgramChange; mevent.Channel = (byte)(eventflag - EventProgramChange); mevent.Instrument = file.ReadByte(); } else if (eventflag >= EventChannelPressure && eventflag < EventChannelPressure + 16) { mevent.EventFlag = EventChannelPressure; mevent.Channel = (byte)(eventflag - EventChannelPressure); mevent.ChanPressure = file.ReadByte(); } else if (eventflag >= EventPitchBend && eventflag < EventPitchBend + 16) { mevent.EventFlag = EventPitchBend; mevent.Channel = (byte)(eventflag - EventPitchBend); mevent.PitchBend = file.ReadShort(); } else if (eventflag == SysexEvent1) { mevent.EventFlag = SysexEvent1; mevent.Metalength = file.ReadVarlen(); mevent.Value = file.ReadBytes(mevent.Metalength); } else if (eventflag == SysexEvent2) { mevent.EventFlag = SysexEvent2; mevent.Metalength = file.ReadVarlen(); mevent.Value = file.ReadBytes(mevent.Metalength); } else if (eventflag == MetaEvent) { mevent.EventFlag = MetaEvent; mevent.Metaevent = file.ReadByte(); mevent.Metalength = file.ReadVarlen(); mevent.Value = file.ReadBytes(mevent.Metalength); if (mevent.Metaevent == MetaEventTimeSignature) { if (mevent.Metalength < 2) { // throw new MidiFileException( // "Meta Event Time Signature len == " + mevent.Metalength + // " != 4", file.GetOffset()); mevent.Numerator = (byte)0; mevent.Denominator = (byte)4; } else if (mevent.Metalength >= 2 && mevent.Metalength < 4) { mevent.Numerator = (byte)mevent.Value[0]; mevent.Denominator = (byte)System.Math.Pow(2, mevent.Value[1]); } else { mevent.Numerator = (byte)mevent.Value[0]; mevent.Denominator = (byte)System.Math.Pow(2, mevent.Value[1]); } } else if (mevent.Metaevent == MetaEventTempo) { if (mevent.Metalength != 3) { throw new MidiFileException( "Meta Event Tempo len == " + mevent.Metalength + " != 3", file.GetOffset()); } mevent.Tempo = ( (mevent.Value[0] << 16) | (mevent.Value[1] << 8) | mevent.Value[2]); } else if (mevent.Metaevent == MetaEventEndOfTrack) { /* break; */ } } else { throw new MidiFileException("Unknown event " + mevent.EventFlag, file.GetOffset()-1); } } return result; }
/** Parse the given Midi file, and return an instance of this MidiFile * class. After reading the midi file, this object will contain: * - The raw list of midi events * - The Time Signature of the song * - All the tracks in the song which contain notes. * - The number, starttime, and duration of each note. */ public void parse(MidiFileReader file, string filename) { string id; int len; this.filename = filename; tracks = new List<MidiTrack>(); trackPerChannel = false; id = file.ReadAscii(4); if (id != "MThd") { throw new MidiFileException("Doesn't start with MThd", 0); } len = file.ReadInt(); if (len != 6) { throw new MidiFileException("Bad MThd header", 4); } trackmode = file.ReadShort(); int num_tracks = file.ReadShort(); quarternote = file.ReadShort(); events = new List<MidiEvent>[num_tracks]; for (int tracknum = 0; tracknum < num_tracks; tracknum++) { events[tracknum] = ReadTrack(file); MidiTrack track = new MidiTrack(events[tracknum], tracknum); if (track.Notes.Count > 0) { tracks.Add(track); } } /* Get the length of the song in pulses */ foreach (MidiTrack track in tracks) { MidiNote last = track.Notes[track.Notes.Count-1]; if (this.totalpulses < last.StartTime + last.Duration) { this.totalpulses = last.StartTime + last.Duration; } } /* If we only have one track with multiple channels, then treat * each channel as a separate track. */ if (tracks.Count == 1 && HasMultipleChannels(tracks[0])) { tracks = SplitChannels(tracks[0], events[tracks[0].Number]); trackPerChannel = true; } CheckStartTimes(tracks); /* Determine the time signature */ int tempo = 0; int numer = 0; int denom = 0; foreach (List<MidiEvent> list in events) { foreach (MidiEvent mevent in list) { if (mevent.Metaevent == MetaEventTempo && tempo == 0) { tempo = mevent.Tempo; } if (mevent.Metaevent == MetaEventTimeSignature && numer == 0) { numer = mevent.Numerator; denom = mevent.Denominator; } } } if (tempo == 0) { tempo = 500000; /* 500,000 microseconds = 0.05 sec */ } if (numer == 0) { numer = 4; denom = 4; } timesig = new TimeSignature(numer, denom, quarternote, tempo); }
public void TestAscii() { byte[] data = new byte[] { 65, 66, 67, 68, 69, 70 }; WriteTestFile(data); MidiFileReader reader = new MidiFileReader(testfile); Assert.AreEqual(reader.GetOffset(), 0); Assert.AreEqual(reader.ReadAscii(3), "ABC"); Assert.AreEqual(reader.GetOffset(), 3); Assert.AreEqual(reader.ReadAscii(3), "DEF"); Assert.AreEqual(reader.GetOffset(), 6); File.Delete(testfile); }