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 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);
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();