Audio and MIDI library for .NET
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

147 lines
5.3 KiB

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Text;
  4. using NAudio.Midi;
  5. namespace AudioFileInspector
  6. {
  7. class MidiFileInspector : IAudioFileInspector
  8. {
  9. #region IAudioFileInspector Members
  10. public string FileExtension
  11. {
  12. get { return ".mid"; }
  13. }
  14. public string FileTypeDescription
  15. {
  16. get { return "Standard MIDI File"; }
  17. }
  18. public string Describe(string fileName)
  19. {
  20. MidiFile mf = new MidiFile(fileName, false);
  21. StringBuilder sb = new StringBuilder();
  22. sb.AppendFormat("Format {0}, Tracks {1}, Delta Ticks Per Quarter Note {2}\r\n",
  23. mf.FileFormat, mf.Tracks, mf.DeltaTicksPerQuarterNote);
  24. int beatsPerMeasure = FindBeatsPerMeasure(mf.Events[0]);
  25. for (int n = 0; n < mf.Tracks; n++)
  26. {
  27. foreach (MidiEvent midiEvent in mf.Events[n])
  28. {
  29. if(!MidiEvent.IsNoteOff(midiEvent))
  30. {
  31. sb.AppendFormat("{0} {1}\r\n", ToMBT(midiEvent.AbsoluteTime, mf.DeltaTicksPerQuarterNote, beatsPerMeasure), midiEvent);
  32. }
  33. }
  34. }
  35. return sb.ToString();
  36. }
  37. private string ToMBT(long absoluteTime, int ticksPerBeat, int beatsPerMeasure)
  38. {
  39. long measure = (absoluteTime / (ticksPerBeat * beatsPerMeasure)) + 1;
  40. long beat = ((absoluteTime / ticksPerBeat) % beatsPerMeasure) + 1;
  41. long tick = absoluteTime % ticksPerBeat;
  42. return String.Format("{0}:{1}:{2}", measure, beat, tick);
  43. }
  44. private string ToMBT(MidiEvent midiEvent, int ticksPerBeat, List<TimeSignatureChange> timeSignatures)
  45. {
  46. TimeSignatureChange latestTimeSig = FindLatestTimeSig(midiEvent.AbsoluteTime,timeSignatures);
  47. long relativeTime = midiEvent.AbsoluteTime - latestTimeSig.AbsoluteTime;
  48. long measure = (relativeTime / (ticksPerBeat * latestTimeSig.BeatsPerMeasure)) + latestTimeSig.StartMeasureNumber;
  49. long beat = ((relativeTime / ticksPerBeat) % latestTimeSig.BeatsPerMeasure) + 1;
  50. long tick = relativeTime % ticksPerBeat;
  51. return String.Format("{0}:{1}:{2}", measure, beat, tick);
  52. }
  53. /// <summary>
  54. /// Find the number of beats per measure
  55. /// (for now assume just one TimeSignature per MIDI track)
  56. /// </summary>
  57. private int FindBeatsPerMeasure(IEnumerable<MidiEvent> midiEvents)
  58. {
  59. int beatsPerMeasure = 4;
  60. foreach (MidiEvent midiEvent in midiEvents)
  61. {
  62. TimeSignatureEvent tse = midiEvent as TimeSignatureEvent;
  63. if (tse != null)
  64. {
  65. beatsPerMeasure = tse.Numerator;
  66. }
  67. }
  68. return beatsPerMeasure;
  69. }
  70. private TimeSignatureChange FindLatestTimeSig(long absoluteTime, List<TimeSignatureChange> timeSignatures)
  71. {
  72. TimeSignatureChange latestChange = null;
  73. foreach (TimeSignatureChange change in timeSignatures)
  74. {
  75. if (absoluteTime >= change.AbsoluteTime)
  76. latestChange = change;
  77. else
  78. break;
  79. }
  80. if (latestChange != null)
  81. {
  82. latestChange = new TimeSignatureChange(0, 4, 1);
  83. }
  84. return latestChange;
  85. }
  86. private List<TimeSignatureChange> FindTimeSignatures(List<MidiEvent> midiEvents)
  87. {
  88. long currentTime = -1;
  89. List<TimeSignatureChange> timeSignatureEvents = new List<TimeSignatureChange>();
  90. foreach (MidiEvent midiEvent in midiEvents)
  91. {
  92. TimeSignatureEvent tse = midiEvent as TimeSignatureEvent;
  93. if (tse != null)
  94. {
  95. if (tse.AbsoluteTime <= currentTime)
  96. throw new ArgumentException("Unsorted Time Signatures found");
  97. // TODO: work out how to get the start measure
  98. int startMeasure = 1;
  99. timeSignatureEvents.Add(new TimeSignatureChange(tse.AbsoluteTime,tse.Numerator,startMeasure));
  100. currentTime = tse.AbsoluteTime;
  101. }
  102. }
  103. return timeSignatureEvents;
  104. }
  105. class TimeSignatureChange
  106. {
  107. long absoluteTime;
  108. int beatsPerMeasure;
  109. int startMeasureNumber;
  110. public long AbsoluteTime
  111. {
  112. get { return absoluteTime; }
  113. }
  114. public int BeatsPerMeasure
  115. {
  116. get { return beatsPerMeasure; }
  117. }
  118. public int StartMeasureNumber
  119. {
  120. get { return startMeasureNumber; }
  121. }
  122. public TimeSignatureChange(long absoluteTime, int beatsPerMeasure, int startMeasureNumber)
  123. {
  124. this.absoluteTime = absoluteTime;
  125. this.beatsPerMeasure = beatsPerMeasure;
  126. this.startMeasureNumber = startMeasureNumber;
  127. }
  128. }
  129. #endregion
  130. }
  131. }