Skip to content

Latest commit

 

History

History
65 lines (47 loc) · 3.24 KB

MidiFile.md

File metadata and controls

65 lines (47 loc) · 3.24 KB

Exploring MIDI Files with MidiFile

The MidiFile class in NAudio allows you to open and examine the MIDI events in a standard MIDI file. It can also be used to create or update MIDI files, but this article focuses on reading.

Opening a MIDI file

Opening a MidiFile is as simple as creating a new MidiFile object and passing in the path. You can choose to enable strictMode which will throw exceptions if various faults are found with the file such as note on events missing a paired note off or controller values out of range.

var strictMode = false;
var mf = new MidiFile(fileName, strictMode);

We can discover what MIDI file format the file is (Type 0 or type 1), as well as how many tracks are present and what the DeltaTicksPerQuarterNote value is.

Console.WriteLine("Format {0}, Tracks {1}, Delta Ticks Per Quarter Note {2}",
                mf.FileFormat, mf.Tracks, mf.DeltaTicksPerQuarterNote);

Examining the MIDI events

The MIDI events can be accessed with the Events property, passing in the index of the track whose events you want to access. This gives you a MidiEventCollection you can iterate through.

All the events in the MIDI file will be represented by a class inheriting from MidiEvent. The MidiFile class will also have set an AbsoluteTime property on each note, which represents the timestamp of the MIDI event from the start of file in terms of delta ticks.

For note on events, MidiFile will also try to pair up the corresponding NoteOffEvent events. This allows you to see the duration of each note (which is simply the difference in time between the absolute time of the NoteOffEvent and NoteOnEvent.

Each MidiEvent has a ToString overload with basic information, so we can print out details of all the events in the file like this. (we don't print out the NoteOffEvent instances, because they are each paired to a NoteOnEvent which reports the duration)

for (int n = 0; n < mf.Tracks; n++)
{
    foreach (var midiEvent in mf.Events[n])
    {
        if(!MidiEvent.IsNoteOff(midiEvent))
        {
            Console.WriteLine("{0} {1}\r\n", ToMBT(midiEvent.AbsoluteTime, mf.DeltaTicksPerQuarterNote, timeSignature), midiEvent);
        }
    }
}

You'll see that a helper ToMBT method is being used above to convert the AbsoluteTime into a more helpful Measures Beats Ticks format. Here's a basic implementation (that doesn't take into account any possible time signature events that might take place)

private string ToMBT(long eventTime, int ticksPerQuarterNote, TimeSignatureEvent timeSignature)
{
    int beatsPerBar = timeSignature == null ? 4 : timeSignature.Numerator;
    int ticksPerBar = timeSignature == null ? ticksPerQuarterNote * 4 : (timeSignature.Numerator * ticksPerQuarterNote * 4) / (1 << timeSignature.Denominator);
    int ticksPerBeat = ticksPerBar / beatsPerBar;
    long bar = 1 + (eventTime / ticksPerBar);
    long beat = 1 + ((eventTime % ticksPerBar) / ticksPerBeat);
    long tick = eventTime % ticksPerBeat;
    return String.Format("{0}:{1}:{2}", bar, beat, tick);
}

Note that to get the TimeSignatureEvent needed by this function we can simply do something like:

var timeSignature = mf.Events[0].OfType<TimeSignatureEvent>().FirstOrDefault();