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

Posted

技术标签:

【中文标题】使用 NAudio 从 MIDI 文件中读取音符【英文标题】:Reading notes from MIDI file using NAudio 【发布时间】:2014-05-27 11:51:09 【问题描述】:

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

            Note noteOn = new Note(); //custom class Note
            MidiFile midi = new MidiFile(open.FileName);
            List<TempoEvent> tempo = new List<TempoEvent>();

            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 的笔记列表,是否?如果我理解正确的话,每个音符都有“开始”和“结束”,开始由NoteOnEvent 定义,而“结束”由NoteOffEvent 定义。如果我查看这两个事件(NoteOnNoteOff),我会得到重复的注释。我说的对吗?

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

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

我不知道参数lastTempoEvent.RealTimetempo。是上次节奏事件的节奏还是?

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

【问题讨论】:

【参考方案1】:

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

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

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

【讨论】:

【参考方案2】:

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

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

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

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

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

【讨论】:

以上是关于使用 NAudio 从 MIDI 文件中读取音符的主要内容,如果未能解决你的问题,请参考以下文章

无法使用 NAudio.Midi 播放音符

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

从 MIDI 中提取音符开始

使用 NAudio 从一段 MIDI 文件中读取 MIDI 事件、消息

您如何确定 NAudio 1.7 中的 midi 文件持续时间(以条为单位)?

NAudio 和 Midi 文件读取