使用NAudio从MIDI文件中读取音符

任务是使用NAudio库从MIDI文件中获取所有音符和时间。 到目前为止,我从文件中获取所有笔记,但我无法得到他们的时间。

Note noteOn = new Note(); //custom class Note MidiFile midi = new MidiFile(open.FileName); List tempo = new List(); for (int i = 0; i < midi.Events.Count(); i++) { foreach (MidiEvent note in midi.Events[i]) { TempoEvent tempoE; try { tempoE = (TempoEvent)note; tempo.Add(tempoE); } catch { } if (note.CommandCode == MidiCommandCode.NoteOn) { var t_note = ( NoteOnEvent)note; var noteOffEvent = t_note.OffEvent; noteOn.NoteName.Add(t_note.NoteName); noteOn.NoteNumber.Add(t_note.NoteNumber); noteOn.NoteVelocity.Add(t_note.Velocity); noteOn.NoteLenght.Add(t_note.NoteLength); double d = (t_note.AbsoluteTime / midi.DeltaTicksPerQuarterNote) * tempo[tempo.Count() - 1].Tempo; noteOn.StartTime.Add(TimeSpan.FromSeconds(d)); } } } 

问题:

1)只获取NoteOnEvents中的注释列表? 如果我理解正确,每个音符都有’start’和’end’,start由NoteOnEvent定义,’end’由NoteOffEvent定义。 如果我查看两个事件( NoteOnNoteOff ),我会得到重复的注释。 我对吗?

2)如何记录笔记的时间? 根据这篇文章 ,我得到了一些值,但似乎第一个音符的时间是正确的,但其他人没有。 同样在这篇文章中,有一条评论说计算时间的公式必须是:

 ((note.AbsTime - lastTempoEvent.AbsTime) / midi.ticksPerQuarterNote) * tempo + lastTempoEvent.RealTime. 

我不知道参数lastTempoEvent.RealTimetempo 。 这是最后一次节奏活动的节奏还是?

3)读取MIDI文件非常慢,对于较小的文件,它可以,但对于大文件则没有。 这个小文件有~150 NoteOnEvents ,这个更大的文件有~1250 NoteOnEvents ,这不是那么“重”。 为什么这么慢?

  1. 在MIDI文件中,音符具有单独的音符开和音符关事件。 NAudio已经搜索相应的音符关闭事件并为您计算长度,因此您不需要自己处理音符事件。 (但是,速度可能会在音符开启和音符关闭事件之间发生变化,因此您必须分别计算两次。)

  2. 这些是值的描述,而不是实际的字段名称。 tempo是最后一个速度事件的MicrosecondsPerQuarterNote值。 lastTempoEvent.RealTime是您为上一个速度事件计算的时间(以微秒为单位)。

    最后一个速度事件是具有最大绝对时间的速度事件,该事件仍然在此事件的绝对时间之前。 该节奏事件可能在另一个轨道中,因此在处理事件之前合并所有轨道(将midi.Events.MidiFileType设置为零)可能是个好主意。

您可以查看提供MIDI文件解析的其他.NET库。 例如,使用DryWetMIDI,您可以使用以下代码获取MIDI文件中包含的所有注释:

 MidiFile file = MidiFile.Read("Great Song.mid"); IEnumerable notes = file.GetNotes(); 

Note具有TimeLength属性。 由MIDI文件的时间分割定义的这些属性返回的值的单位。 但是你可以用更易于理解的格式获得笔记的时间和长度。 您可以写几小时,几分钟,几秒钟

 TempoMap tempoMap = file.GetTempoMap(); MetricTimeSpan metricTime = note.TimeAs(tempoMap); MetricTimeSpan metricLength = note.LengthAs(tempoMap); 

使用TimeAsLengthAs方法,您无需自己进行任何计算。 MetricTimeSpan实例可以隐式转换为TimeSpan