C#解析MIDI文件 C#如何读取.mid音乐文件的音符数据

4次阅读

naudio 的 midifile 类可直接加载并遍历 midi 音符事件,需调用 gettrackevents() 获取轨道事件,筛选 noteonevent(含力度为0的)和 noteoffevent,以 (channel, notenumber) 为键配对起止时间,注意格式0/1差异及 deltaticks 换算。

C#解析MIDI文件 C#如何读取.mid音乐文件的音符数据

MidiFile 类(NAudio)直接加载并遍历音符事件

NAudio 是 C# 中最稳定、文档最清晰的 MIDI 解析库,MidiFile 类能正确处理标准格式 0 和格式 1 的 .mid 文件。它不播放音频,只解析结构,适合提取音符起止、通道、力度等原始数据。

关键点:必须调用 GetTrackEvents() 获取每个轨道的事件列表,再筛选出 NoteOnEventNoteOffEvent;注意 NoteOnEvent 力度为 0 时等效于 NoteOffEvent(这是 MIDI 标准行为,不是 bug)。

  • 安装 NAudio:dotnet add package NAudio
  • 读取后需检查 MidiFile.format 判断是格式 0(单轨合并)还是格式 1(多轨分离),影响遍历逻辑
  • 时间戳单位是 DeltaTicks,需结合 MidiFile.DeltaTicksPerQuarterNote 换算为真实时间(如需秒级精度)
  • 避免直接遍历 Events 属性——它已被弃用,应使用 GetTrackEvents()

如何识别和配对 NoteOn/NoteOff(含通道与音高)

一个音符由“按下”和“释放”两个事件组成,但 MIDI 允许它们出现在不同轨道、甚至同一轨道中顺序错乱(尤其格式 1 多轨文件)。不能简单按顺序两两配对。

实操建议:先收集所有 NoteOnEvent(力度 > 0),用 (Channel, NoteNumber) 作键存入字典,值为起始 tick;遇到同键的 NoteOnEvent(力度 = 0)或 NoteOffEvent 时,立即取出并生成完整音符片段(start tick、end tick、duration、velocity)。

  • NoteOnEventData1 是音高(0–127),Data2 是力度(0–127)
  • NoteOffEventData2 是释放速度(常为 0 或忽略),实际不用
  • 某些设备导出的文件可能缺失 NoteOffEvent,只靠力度为 0 的 NoteOnEvent,务必同时监听这两类

处理常见错误:空轨道、无音符事件、格式不支持

打开 .mid 却读不到音符?大概率不是代码错,而是文件本身问题。NAudio 不会抛异常,但 GetTrackEvents() 可能返回空集合或只含 MetaEvent(如 TextEventTempoEvent)。

  • 先检查 MidiFile.Tracks 数量是否 ≥ 1,再逐个调用 GetTrackEvents(i) 确认非空
  • 打印前几条事件的 GetType().NameToString(),确认是否存在 NoteOnEvent
  • 极少数老游戏或硬件导出的格式 2 文件(多序列独立播放)不被 NAudio 支持,会静默跳过——此时 MidiFile.Format 返回 2,需换用 DryWetMIDI
  • 若只有 ProgramChangeEvent 没有音符,说明该轨道只是设置音色,实际演奏在别轨

DryWetMIDI 作为 NAudio 的补充方案(支持格式 2 和更细粒度控制)

当 NAudio 无法满足需求(比如要精确到微秒的时间戳、处理 SysEx 事件、或解析格式 2),DryWetMIDI 是更现代的选择。它 API 更直观,事件模型更严格区分类型,且默认将 NoteOn(力度=0)自动转为 NoteOff

  • 安装:dotnet add package Melanchall.DryWetMidi
  • 核心类是 MidiFile.Read()GetNotes(),一行就能拿到全部音符对象(含 TimeSpan 起止时间)
  • 注意:它的 TimeSpan 基于文件内 tempo map 计算,比手动换算 DeltaTicks 更准,但内存占用略高
  • 若需修改再写回文件,DryWetMIDI 支持完整读-改-写流程,NAudio 则只读不写

真正难的不是“怎么读”,而是判断哪条轨道承载主旋律、如何对齐多轨时间、以及处理 tempo 变化导致的节奏偏移——这些都得结合具体文件结构做逻辑判断,没有通用解法。

text=ZqhQzanResources