2025最强MIDI文件处理指南:从读取到创建的完整工作流
【免费下载链接】NAudio Audio and MIDI library for .NET 项目地址: https://gitcode.com/gh_mirrors/na/NAudio
引言:告别MIDI处理的9大痛点
你是否还在为以下MIDI(Musical Instrument Digital Interface,音乐设备数字接口)处理问题而困扰?
- 无法准确解析复杂MIDI文件结构,导致音轨丢失或事件错乱
- 创建MIDI文件时不知如何组织多音轨事件,时序控制困难
- 处理大型MIDI文件时性能低下,事件排序耗时严重
- 不理解MIDI事件格式,无法正确生成Note On/Off等关键事件
- 多音轨同步问题导致播放时节奏混乱
- 元数据处理不当,无法正确设置 tempo(速度)和调号
- 导出文件时格式错误,导致在专业音乐软件中无法打开
- 对MIDI 1.0规范理解不深入,兼容性问题频发
- 缺乏完整的工作流示例,从理论到实践困难重重
本文将通过12个实战案例和7个完整代码示例,系统讲解如何使用NAudio库处理MIDI文件,从基础读取到高级创建,构建专业级MIDI文件处理能力。
一、MIDI文件核心概念与NAudio架构
1.1 MIDI文件格式解析
MIDI文件本质是一种事件序列,而非音频数据。NAudio支持三种主要MIDI文件格式:
| 格式类型 | 描述 | 适用场景 | 最大音轨数 |
|---|---|---|---|
| Type 0 | 单音轨格式,所有事件在一个音轨中 | 简单旋律、铃声、单个乐器序列 | 1 |
| Type 1 | 多音轨同步格式,所有音轨同步播放 | 歌曲创作、多乐器编排 | 任意 |
| Type 2 | 多音轨异步格式,音轨独立播放 | 多声部独立作品(罕见) | 任意 |
技术细节:MIDI文件以"chunk(块)"组织,包含一个"MThd"头块和多个"MTrk"音轨块。头块定义文件格式、音轨数和时间分辨率(ticks per quarter note)。
1.2 NAudio MIDI处理核心类架构
1.3 关键时间单位解析
MIDI使用两种时间表示方式:
- Delta Time:事件之间的相对时间,存储为可变长度整数(VarInt)
- Absolute Time:从文件开始的绝对时间,NAudio自动计算和维护
时间分辨率由DeltaTicksPerQuarterNote(每四分音符的 ticks 数)决定,常见值为96、120、240或480。例如,在96 ticks/四分音符的设置下:
- 八分音符 = 48 ticks
- 十六分音符 = 24 ticks
- 附点四分音符 = 144 ticks
二、MIDI文件读取与解析实战
2.1 基础读取:获取MIDI文件信息
using NAudio.Midi;
// 读取MIDI文件
var midiFile = new MidiFile("input.mid");
// 输出基本信息
Console.WriteLine($"MIDI File Information:");
Console.WriteLine($"Format: {midiFile.FileFormat}");
Console.WriteLine($"Tracks: {midiFile.Tracks}");
Console.WriteLine($"Resolution: {midiFile.DeltaTicksPerQuarterNote} ticks/quarter note");
// 遍历所有音轨事件
for (int track = 0; track < midiFile.Tracks; track++)
{
Console.WriteLine($"\nTrack {track + 1} events:");
foreach (var midiEvent in midiFile.Events[track])
{
Console.WriteLine($"{midiEvent.AbsoluteTime}ms: {midiEvent}");
}
}
性能提示:对于大型MIDI文件(>10000事件),使用
strictChecking: false构造函数参数可跳过严格验证,提高加载速度。
2.2 事件类型识别与过滤
MIDI事件类型繁多,NAudio将其封装为不同的类。以下代码演示如何识别和处理常见事件类型:
foreach (var midiEvent in midiFile.Events[0])
{
switch (midiEvent.CommandCode)
{
case MidiCommandCode.NoteOn:
var noteOn = (NoteOnEvent)midiEvent;
Console.WriteLine($"Note On: Channel {noteOn.Channel}, Note {noteOn.NoteNumber} " +
$"({MidiEvent.NoteNumberToFrequency(noteOn.NoteNumber):F2}Hz), " +
$"Velocity {noteOn.Velocity}");
break;
case MidiCommandCode.ControlChange:
var cc = (ControlChangeEvent)midiEvent;
Console.WriteLine($"Control Change: Channel {cc.Channel}, " +
$"Controller {cc.Controller} ({cc.ControllerToString()}), " +
$"Value {cc.ControlValue}");
break;
case MidiCommandCode.MetaEvent:
var meta = (MetaEvent)midiEvent;
if (meta.MetaEventType == MetaEventType.Tempo)
{
int bpm = 60000000 / meta.AbsoluteTime; // 计算BPM
Console.WriteLine($"Tempo Change: {bpm} BPM");
}
break;
}
}
技术细节:Note On事件速度为0时,在MIDI规范中等同于Note Off事件,NAudio通过
MidiEvent.IsNoteOff()方法统一识别。
2.3 实战案例:提取MIDI文件信息
以下代码实现一个完整的MIDI文件分析器,提取关键音乐信息:
public class MidiAnalyzer
{
public static void Analyze(string filePath)
{
using (var midiFile = new MidiFile(filePath, strictChecking: false))
{
Console.WriteLine($"MIDI File Analysis: {Path.GetFileName(filePath)}");
Console.WriteLine("=".PadRight(50, '='));
Console.WriteLine($"Format Type: {midiFile.FileFormat}");
Console.WriteLine($"Track Count: {midiFile.Tracks}");
Console.WriteLine($"Resolution: {midiFile.DeltaTicksPerQuarterNote} ticks/quarter note");
for (int track = 0; track < midiFile.Tracks; track++)
{
var trackEvents = midiFile.Events[track];
var stats = new TrackStats();
foreach (var evt in trackEvents)
{
stats.TotalEvents++;
if (evt is NoteEvent noteEvent)
{
if (MidiEvent.IsNoteOn(noteEvent)) stats.NoteOnCount++;
if (MidiEvent.IsNoteOff(noteEvent)) stats.NoteOffCount++;
stats.MinNote = Math.Min(stats.MinNote, noteEvent.NoteNumber);
stats.MaxNote = Math.Max(stats.MaxNote, noteEvent.NoteNumber);
}
else if (evt is MetaEvent metaEvent)
{
switch (metaEvent.MetaEventType)
{
case MetaEventType.Text:
case MetaEventType.TrackName:
stats.TrackName = metaEvent.Text;
break;
case MetaEventType.Tempo:
stats.TempoChanges++;
break;
case MetaEventType.TimeSignature:
stats.TimeSignatures++;
break;
}
}
}
Console.WriteLine($"\nTrack {track + 1}: {stats.TrackName ?? "Unnamed"}");
Console.WriteLine($" Events: {stats.TotalEvents}");
Console.WriteLine($" Notes: {stats.NoteOnCount} notes, Range: {stats.MinNote}~{stats.MaxNote}");
Console.WriteLine($" Tempo Changes: {stats.TempoChanges}, Time Signatures: {stats.TimeSignatures}");
}
}
}
private class TrackStats
{
public string TrackName { get; set; }
public int TotalEvents { get; set; }
public int NoteOnCount { get; set; }
public int NoteOffCount { get; set; }
public int MinNote { get; set; } = 127;
public int MaxNote { get; set; } = 0;
public int TempoChanges { get; set; }
public int TimeSignatures { get; set; }
}
}
// 使用方法
MidiAnalyzer.Analyze("example.mid");
二、MIDI事件处理高级技术
2.1 事件时间转换与量化
音乐时间与MIDI ticks转换是核心技能:
public static class MidiTimeConverter
{
// 将小节-拍-滴答转换为绝对ticks
public static long MeasureBeatToTicks(int measure, int beat, int tick,
int beatsPerMeasure, int ticksPerBeat,
long startTick = 0)
{
return startTick + (measure - 1) * beatsPerMeasure * ticksPerBeat +
(beat - 1) * ticksPerBeat + tick;
}
// 将ticks转换为毫秒(基于速度)
public static double TicksToMilliseconds(long ticks, int ticksPerQuarterNote, int bpm)
{
double quarterNoteMs = 60000.0 / bpm; // 四分音符毫秒数
double tickMs = quarterNoteMs / ticksPerQuarterNote; // 每个tick的毫秒数
return ticks * tickMs;
}
// 量化事件时间(修正演奏误差)
public static void QuantizeEvents(IList<MidiEvent> events, int quantizationValue,
int ticksPerQuarterNote)
{
foreach (var evt in events)
{
if (evt.CommandCode != MidiCommandCode.MetaEvent)
{
long quantizedTime = (evt.AbsoluteTime / quantizationValue) * quantizationValue;
evt.AbsoluteTime = quantizedTime;
}
}
}
}
应用示例:将事件量化为八分音符(假设ticksPerQuarterNote=96):
MidiTimeConverter.QuantizeEvents(trackEvents, 48, 96);
2.2 多音轨事件同步与合并
处理Type 1 MIDI文件时,保持音轨同步至关重要:
public static MidiEventCollection MergeMidiFiles(params string[] filePaths)
{
if (filePaths.Length == 0) return null;
// 加载第一个文件作为基础
var baseFile = new MidiFile(filePaths[0]);
var mergedEvents = new MidiEventCollection(1, baseFile.DeltaTicksPerQuarterNote);
// 添加基础文件的所有音轨
for (int i = 0; i < baseFile.Tracks; i++)
{
mergedEvents.AddTrack(baseFile.Events[i]);
}
// 合并其他文件
foreach (var filePath in filePaths.Skip(1))
{
var midiFile = new MidiFile(filePath);
// 确保时间分辨率一致
if (midiFile.DeltaTicksPerQuarterNote != mergedEvents.DeltaTicksPerQuarterNote)
{
throw new InvalidOperationException("Cannot merge MIDI files with different resolutions");
}
// 添加所有音轨,偏移绝对时间以避免重叠
long timeOffset = GetMaxAbsoluteTime(mergedEvents) + mergedEvents.DeltaTicksPerQuarterNote * 4;
for (int i = 0; i < midiFile.Tracks; i++)
{
var newTrack = mergedEvents.AddTrack();
foreach (var evt in midiFile.Events[i])
{
var clonedEvent = evt.Clone();
clonedEvent.AbsoluteTime += timeOffset;
newTrack.Add(clonedEvent);
}
}
}
mergedEvents.PrepareForExport();
return mergedEvents;
}
private static long GetMaxAbsoluteTime(MidiEventCollection events)
{
long maxTime = 0;
foreach (var track in events)
{
if (track.Count == 0) continue;
maxTime = Math.Max(maxTime, track[track.Count - 1].AbsoluteTime);
}
return maxTime;
}
三、MIDI文件创建完整工作流
3.1 从零开始创建MIDI文件
以下是创建包含多音轨、多种事件的完整MIDI文件示例:
public static void CreateSampleMidiFile(string outputPath)
{
// 创建事件集合(Type 1格式,96 ticks/四分音符)
var events = new MidiEventCollection(1, 96);
// 添加速度和元数据音轨(音轨0)
var metaTrack = events.AddTrack();
AddMetaEvents(metaTrack, events.DeltaTicksPerQuarterNote);
// 添加钢琴音轨(音轨1)
var pianoTrack = events.AddTrack();
AddPianoMelody(pianoTrack, events.DeltaTicksPerQuarterNote);
// 添加贝司音轨(音轨2)
var bassTrack = events.AddTrack();
AddBassLine(bassTrack, events.DeltaTicksPerQuarterNote);
// 准备导出(排序事件、添加结束事件)
events.PrepareForExport();
// 导出为MIDI文件
MidiFile.Export(outputPath, events);
}
private static void AddMetaEvents(IList<MidiEvent> track, int ticksPerQuarterNote)
{
long absoluteTime = 0;
// 添加轨道名称
track.Add(new TextEvent("Metadata Track", MetaEventType.TrackName, absoluteTime));
// 设置速度(120 BPM)
// TempoEvent的构造函数参数是微秒每四分音符
int microsecondsPerQuarterNote = 60000000 / 120; // 120 BPM
track.Add(new TempoEvent(microsecondsPerQuarterNote, absoluteTime));
// 设置拍号(4/4拍)
track.Add(new TimeSignatureEvent(4, 4, 24, 8, absoluteTime));
// 设置调号(C大调)
track.Add(new KeySignatureEvent(0, false, absoluteTime));
}
private static void AddPianoMelody(IList<MidiEvent> track, int ticksPerQuarterNote)
{
// 添加轨道名称
track.Add(new TextEvent("Piano", MetaEventType.TrackName, 0));
// 设置乐器(Acoustic Grand Piano)
track.Add(new PatchChangeEvent(0, 1, 0)); // 通道1,音色0
long time = 0;
int[] melodyNotes = { 60, 62, 64, 65, 67, 69, 71, 72 }; // C大调音阶
// 添加上行音阶
foreach (var note in melodyNotes)
{
// 添加Note On事件(力度80)
track.Add(new NoteOnEvent(time, 1, note, 80, ticksPerQuarterNote / 2));
// 移动到下一个八分音符
time += ticksPerQuarterNote / 2;
}
// 添加下行音阶(带附点四分音符)
for (int i = melodyNotes.Length - 1; i >= 0; i--)
{
int duration = (i % 2 == 0) ? ticksPerQuarterNote * 3 / 2 : ticksPerQuarterNote / 2;
track.Add(new NoteOnEvent(time, 1, melodyNotes[i], 80, duration));
time += duration;
}
// 添加和弦(C大三和弦)
track.Add(new NoteOnEvent(time, 1, 60, 75, ticksPerQuarterNote * 2)); // C4
track.Add(new NoteOnEvent(time, 1, 64, 75, ticksPerQuarterNote * 2)); // E4
track.Add(new NoteOnEvent(time, 1, 67, 75, ticksPerQuarterNote * 2)); // G4
}
private static void AddBassLine(IList<MidiEvent> track, int ticksPerQuarterNote)
{
// 添加轨道名称
track.Add(new TextEvent("Bass", MetaEventType.TrackName, 0));
// 设置乐器(Acoustic Bass)
track.Add(new PatchChangeEvent(0, 2, 33)); // 通道2,音色33
long time = 0;
int[] bassNotes = { 36, 36, 38, 36, 41, 38, 36, 36 }; // 低音线
foreach (var note in bassNotes)
{
track.Add(new NoteOnEvent(time, 2, note, 70, ticksPerQuarterNote));
time += ticksPerQuarterNote; // 每个音符持续一个四分音符
}
}
3.2 MIDI文件创建流程图
3.3 高级应用:程序生成音乐
以下示例生成一个随机但和谐的音乐片段:
public static void GenerateRandomMusic(string outputPath, int measureCount = 16)
{
var rng = new Random();
var events = new MidiEventCollection(1, 96);
// 添加元数据
var metaTrack = events.AddTrack();
metaTrack.Add(new TempoEvent(60000000 / 110, 0)); // 110 BPM
metaTrack.Add(new TimeSignatureEvent(4, 4, 24, 8, 0));
metaTrack.Add(new KeySignatureEvent(0, false, 0));
// 创建和弦进行(I-IV-V-vi进行)
int[] chordProgression = { 0, 5, 7, 3 }; // C, F, G, Am
// 添加钢琴和弦轨
var pianoTrack = events.AddTrack();
pianoTrack.Add(new PatchChangeEvent(0, 1, 0)); // 钢琴音色
// 添加旋律轨
var melodyTrack = events.AddTrack();
melodyTrack.Add(new PatchChangeEvent(0, 2, 5)); // 小提琴音色
long time = 0;
int ticksPerMeasure = 96 * 4; // 4/4拍,每小节4个四分音符
for (int measure = 0; measure < measureCount; measure++)
{
// 当前和弦(C大调音阶)
int chordRoot = chordProgression[measure % chordProgression.Length];
int[] chordNotes = GetChordNotes(chordRoot);
// 添加和弦
AddChord(pianoTrack, time, chordNotes, 1, 96 * 4); // 持续整个小节
// 添加旋律
AddRandomMelody(melodyTrack, time, chordNotes, rng);
time += ticksPerMeasure;
}
events.PrepareForExport();
MidiFile.Export(outputPath, events);
}
private static int[] GetChordNotes(int root)
{
// 返回C大调音阶上的三和弦
// 根音 + 三音 + 五音
return new[] { root, root + 4, root + 7 };
}
private static void AddChord(IList<MidiEvent> track, long time, int[] notes,
int channel, int duration)
{
foreach (var note in notes)
{
// 将和弦音高移至中低音区
int midiNote = note + 60; // C4及以上
track.Add(new NoteOnEvent(time, channel, midiNote, 75, duration));
}
}
private static void AddRandomMelody(IList<MidiEvent> track, long time,
int[] chordNotes, Random rng)
{
int[] rhythmPattern = { 24, 24, 24, 24, 48, 48 }; // 十六分音符和八分音符组合
int channel = 2;
foreach (var duration in rhythmPattern)
{
// 从和弦音中随机选择一个音
int noteIndex = rng.Next(chordNotes.Length);
int octaveShift = rng.Next(2) == 0 ? 12 : 24; // 高八度或两个八度
int midiNote = chordNotes[noteIndex] + 60 + octaveShift;
// 随机力度
int velocity = rng.Next(60, 90);
track.Add(new NoteOnEvent(time, channel, midiNote, velocity, duration));
time += duration;
}
}
四、性能优化与高级应用
4.1 大型MIDI文件处理优化
处理包含数千音轨和数百万事件的大型MIDI文件时,需注意性能优化:
public static class MidiPerformanceOptimizations
{
// 高效合并多个MIDI文件
public static MidiEventCollection MergeLargeMidiFiles(IEnumerable<string> filePaths)
{
// 1. 首先收集所有文件的基本信息,避免同时打开所有文件
var fileInfos = new List<(int Resolution, long MaxTime)>();
int commonResolution = -1;
foreach (var path in filePaths)
{
using (var tempFile = new MidiFile(path))
{
if (commonResolution == -1)
{
commonResolution = tempFile.DeltaTicksPerQuarterNote;
}
else if (tempFile.DeltaTicksPerQuarterNote != commonResolution)
{
throw new InvalidOperationException("All files must have the same resolution");
}
long maxTime = 0;
for (int i = 0; i < tempFile.Tracks; i++)
{
if (tempFile.Events[i].Count > 0)
{
maxTime = Math.Max(maxTime, tempFile.Events[i].Last().AbsoluteTime);
}
}
fileInfos.Add((commonResolution, maxTime));
}
}
// 2. 创建合并后的事件集合
var mergedEvents = new MidiEventCollection(1, commonResolution);
long currentOffset = 0;
// 3. 逐个加载并合并文件,释放资源后再加载下一个
int fileIndex = 0;
foreach (var path in filePaths)
{
using (var midiFile = new MidiFile(path))
{
for (int i = 0; i < midiFile.Tracks; i++)
{
var newTrack = mergedEvents.AddTrack();
foreach (var evt in midiFile.Events[i])
{
var clonedEvent = evt.Clone();
clonedEvent.AbsoluteTime += currentOffset;
newTrack.Add(clonedEvent);
}
}
}
// 更新偏移量
currentOffset += fileInfos[fileIndex].MaxTime + commonResolution * 4; // 添加4拍间隔
fileIndex++;
}
mergedEvents.PrepareForExport();
return mergedEvents;
}
// 事件过滤与压缩
public static void FilterAndCompressEvents(MidiEventCollection events,
int minVelocity = 20,
int quantization = 12)
{
foreach (var track in events)
{
// 创建新事件列表,避免修改正在迭代的集合
var newEvents = new List<MidiEvent>();
foreach (var evt in track)
{
// 过滤弱音事件
if (evt is NoteEvent noteEvent && noteEvent.Velocity < minVelocity)
{
continue;
}
// 量化非元事件
if (evt.CommandCode != MidiCommandCode.MetaEvent)
{
evt.AbsoluteTime = (evt.AbsoluteTime / quantization) * quantization;
}
newEvents.Add(evt);
}
// 替换原事件列表
track.Clear();
foreach (var evt in newEvents)
{
track.Add(evt);
}
}
}
}
4.2 MIDI文件与音乐理论结合应用
使用NAudio实现音乐理论功能:
public static class MidiMusicTheory
{
// 计算音程
public static int CalculateInterval(int note1, int note2)
{
int interval = Math.Abs(note1 - note2) % 12;
return interval switch
{
0 => 1, // 同度
1 => 2, // 小二度
2 => 3, // 大二度
3 => 4, // 小三度
4 => 5, // 大三度
5 => 6, // 纯四度
6 => 7, // 增四度/减五度
7 => 8, // 纯五度
8 => 9, // 小六度
9 => 10, // 大六度
10 => 11, // 小七度
11 => 12, // 大七度
_ => 1 // 不可能发生
};
}
// 分析和弦进行
public static List<string> AnalyzeChordProgression(MidiFile midiFile)
{
var chordProgression = new List<string>();
var key = DetermineKey(midiFile);
// 实现复杂的和弦识别逻辑...
return chordProgression;
}
// 确定MIDI文件的调式
public static KeySignature DetermineKey(MidiFile midiFile)
{
// 实现调式识别算法...
return new KeySignature(0, false); // 默认C大调
}
// 和弦结构类
public class KeySignature
{
public int SharpsFlats { get; }
public bool IsMinor { get; }
public KeySignature(int sharpsFlats, bool isMinor)
{
SharpsFlats = sharpsFlats;
IsMinor = isMinor;
}
}
}
五、实战案例:MIDI文件批量处理器
以下是一个完整的MIDI文件批量处理工具,支持转格式、变调、变速等功能:
public class MidiBatchProcessor
{
public enum ProcessingAction
{
ConvertToType0,
Transpose,
ChangeTempo,
ExtractPianoTracks,
Quantize
}
public class ProcessingOptions
{
public ProcessingAction Action { get; set; }
public int TransposeSemitones { get; set; } = 0;
public double TempoMultiplier { get; set; } = 1.0;
public int QuantizationValue { get; set; } = 24; // 十六分音符
}
public void ProcessFiles(IEnumerable<string> inputPaths, string outputDirectory,
ProcessingOptions options)
{
foreach (var inputPath in inputPaths)
{
try
{
ProcessSingleFile(inputPath, outputDirectory, options);
Console.WriteLine($"Processed: {Path.GetFileName(inputPath)}");
}
catch (Exception ex)
{
Console.WriteLine($"Error processing {Path.GetFileName(inputPath)}: {ex.Message}");
}
}
}
private void ProcessSingleFile(string inputPath, string outputDirectory,
ProcessingOptions options)
{
using (var midiFile = new MidiFile(inputPath))
{
// 创建新的事件集合
var newEvents = new MidiEventCollection(
options.Action == ProcessingOptions.ProcessingAction.ConvertToType0 ? 0 : midiFile.FileFormat,
midiFile.DeltaTicksPerQuarterNote);
// 复制并处理事件
for (int track = 0; track < midiFile.Tracks; track++)
{
var originalTrack = midiFile.Events[track];
// 根据选项决定是否处理此音轨
if (options.Action == ProcessingOptions.ProcessingAction.ExtractPianoTracks)
{
if (!IsPianoTrack(originalTrack))
continue;
}
var newTrack = newEvents.AddTrack();
foreach (var evt in originalTrack)
{
var processedEvent = ProcessEvent(evt, options, midiFile.DeltaTicksPerQuarterNote);
if (processedEvent != null)
{
newTrack.Add(processedEvent);
}
}
}
newEvents.PrepareForExport();
// 创建输出路径
string fileName = Path.GetFileNameWithoutExtension(inputPath);
string outputPath = Path.Combine(outputDirectory, $"{fileName}_processed.mid");
// 导出处理后的文件
MidiFile.Export(outputPath, newEvents);
}
}
private MidiEvent ProcessEvent(MidiEvent evt, ProcessingOptions options, int ticksPerQuarterNote)
{
var clonedEvent = evt.Clone();
// 变调处理
if (options.Action == ProcessingOptions.ProcessingAction.Transpose &&
options.TransposeSemitones != 0 && evt is NoteEvent noteEvent)
{
int newNoteNumber = noteEvent.NoteNumber + options.TransposeSemitones;
if (newNoteNumber >= 0 && newNoteNumber <= 127)
{
noteEvent.NoteNumber = newNoteNumber;
}
}
// 变速处理
if (options.Action == ProcessingOptions.ProcessingAction.ChangeTempo &&
Math.Abs(options.TempoMultiplier - 1.0) > 0.001)
{
if (evt is TempoEvent tempoEvent)
{
// 调整速度事件
int newTempo = (int)(tempoEvent.MicrosecondsPerQuarterNote / options.TempoMultiplier);
return new TempoEvent(newTempo, evt.AbsoluteTime);
}
else
{
// 调整所有事件的时间
clonedEvent.AbsoluteTime = (long)(evt.AbsoluteTime / options.TempoMultiplier);
}
}
// 量化处理
if (options.Action == ProcessingOptions.ProcessingAction.Quantize &&
evt.CommandCode != MidiCommandCode.MetaEvent)
{
clonedEvent.AbsoluteTime = (clonedEvent.AbsoluteTime / options.QuantizationValue) *
options.QuantizationValue;
}
return clonedEvent;
}
private bool IsPianoTrack(IList<MidiEvent> track)
{
// 判断是否为钢琴音轨(简单实现)
foreach (var evt in track)
{
if (evt is PatchChangeEvent patchEvent)
{
// 0-8通常是钢琴类乐器
return patchEvent.Patch >= 0 && patchEvent.Patch <= 8;
}
}
return false;
}
}
// 使用示例
var processor = new MidiBatchProcessor();
var options = new MidiBatchProcessor.ProcessingOptions
{
Action = MidiBatchProcessor.ProcessingAction.Transpose,
TransposeSemitones = 2 // 升高2个半音
};
processor.ProcessFiles(new[] { "song1.mid", "song2.mid" }, "processed_output", options);
结语:掌握MIDI文件处理的下一步
通过本文的学习,您已经掌握了使用NAudio处理MIDI文件的核心技术。要进一步提升技能,建议:
- 深入学习MIDI 1.0规范:理解所有事件类型和元数据格式
- 研究音乐理论:了解和弦结构、调式和音乐形式对MIDI事件组织的影响
- 探索高级应用:如MIDI到音频的合成、实时MIDI处理、音乐信息检索等
- 分析复杂MIDI文件:研究专业音乐软件生成的MIDI文件结构和事件组织
NAudio库为.NET开发者提供了强大的MIDI处理能力,结合音乐理论知识,可以构建从简单MIDI播放器到专业音乐创作软件的各种应用。
扩展资源:
- NAudio官方文档:https://github.com/naudio/NAudio
- MIDI 1.0规范:https://www.midi.org/specifications-old/item/the-midi-1-0-specification
- Music Theory for Programmers:探索音乐理论与代码结合的高级主题
希望本文能帮助您构建专业级MIDI文件处理应用,释放音乐创造力!
如果您觉得本文有价值,请点赞、收藏并关注作者,获取更多音频处理技术分享。下一篇将探讨"NAudio实时音频流与MIDI合成技术",敬请期待!
【免费下载链接】NAudio Audio and MIDI library for .NET 项目地址: https://gitcode.com/gh_mirrors/na/NAudio
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



