0

0

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

畫卷琴夢

畫卷琴夢

发布时间:2026-03-05 11:35:16

|

222人浏览过

|

来源于php中文网

原创

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 变化导致的节奏偏移——这些都得结合具体文件结构做逻辑判断,没有通用解法。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
format在python中的用法
format在python中的用法

Python中的format是一种字符串格式化方法,用于将变量或值插入到字符串中的占位符位置。通过format方法,我们可以动态地构建字符串,使其包含不同值。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

866

2023.07.31

python中的format是什么意思
python中的format是什么意思

python中的format是一种字符串格式化方法,用于将变量或值插入到字符串中的占位符位置。通过format方法,我们可以动态地构建字符串,使其包含不同值。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

457

2024.06.27

golang map内存释放
golang map内存释放

本专题整合了golang map内存相关教程,阅读专题下面的文章了解更多相关内容。

77

2025.09.05

golang map相关教程
golang map相关教程

本专题整合了golang map相关教程,阅读专题下面的文章了解更多详细内容。

39

2025.11.16

golang map原理
golang map原理

本专题整合了golang map相关内容,阅读专题下面的文章了解更多详细内容。

67

2025.11.17

java判断map相关教程
java判断map相关教程

本专题整合了java判断map相关教程,阅读专题下面的文章了解更多详细内容。

47

2025.11.27

Golang channel原理
Golang channel原理

本专题整合了Golang channel通信相关介绍,阅读专题下面的文章了解更多详细内容。

258

2025.11.14

golang channel相关教程
golang channel相关教程

本专题整合了golang处理channel相关教程,阅读专题下面的文章了解更多详细内容。

351

2025.11.17

Rust内存安全机制与所有权模型深度实践
Rust内存安全机制与所有权模型深度实践

本专题围绕 Rust 语言核心特性展开,深入讲解所有权机制、借用规则、生命周期管理以及智能指针等关键概念。通过系统级开发案例,分析内存安全保障原理与零成本抽象优势,并结合并发场景讲解 Send 与 Sync 特性实现机制。帮助开发者真正理解 Rust 的设计哲学,掌握在高性能与安全性并重场景中的工程实践能力。

2

2026.03.05

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Excel 教程
Excel 教程

共162课时 | 20.1万人学习

C# 教程
C# 教程

共94课时 | 10.7万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号