求助:使用Drywetmidi调整MIDI播放时音符音高无效果
播放期间修改MIDI文件所有音符音高的解决方案
你用midiFile.ProcessNotes()修改的是磁盘或内存里的原始MIDI文件对象,但如果播放已经启动,播放引擎大概率已经把MIDI事件加载到缓冲区或者正在直接读取原始数据流,修改原对象根本同步不到正在运行的播放进程里,所以才没效果。
下面给你几种可行的解决办法:
方法1:用播放库的实时事件拦截(最推荐)
大部分MIDI播放库(比如你代码里用到的DryWetMIDI)都支持播放时拦截每个MIDI事件,实时修改音高后再发送到输出设备。示例代码:
using Melanchall.DryWetMidi.Multimedia; using Melanchall.DryWetMidi.Core; // 初始化播放设备,替换成你的设备名称 var outputDevice = OutputDevice.GetByName("你的MIDI输出设备"); var player = new MidiPlayer(outputDevice); // 加载目标MIDI文件 var midiFile = MidiFile.Read("你的MIDI文件路径.mid"); // 注册播放时的事件处理逻辑 player.EventSent += (sender, e) => { // 处理NoteOn事件,修改音高(这里是降半音,注意限制在0-127范围内) if (e.Event is NoteOnEvent noteOn) { noteOn.NoteNumber = (SevenBitNumber)Math.Clamp(noteOn.NoteNumber - 1, 0, 127); } // 同步处理NoteOff事件,避免音符残留 else if (e.Event is NoteOffEvent noteOff) { noteOff.NoteNumber = (SevenBitNumber)Math.Clamp(noteOff.NoteNumber - 1, 0, 127); } }; // 启动播放 player.Play(midiFile);
方法2:暂停后修改文件再重新播放
如果你的播放库不支持实时拦截,可以先暂停播放,修改MIDI文件对象后重新启动播放:
// 按钮点击时的处理逻辑 if (player.IsPlaying) { player.Stop(); // 修改所有音符的音高,同时同步NoteOff事件 midiFile.ProcessNotes(n => { n.NoteNumber = (SevenBitNumber)Math.Clamp(n.NoteNumber - 1, 0, 127); foreach (var noteOff in n.NoteOffEvents) { noteOff.NoteNumber = n.NoteNumber; } }); // 用修改后的文件重新播放 player.Play(midiFile); }
方法3:用MIDI弯音轮批量调整音高
还有一种更高效的方式:发送MIDI弯音轮消息,一次性调整所有正在播放的音符音高,不用逐个修改音符事件:
// 降半音:弯音轮默认范围是±2半音,0x4000是默认音高,0x3000对应降1半音 var pitchBendDown = new PitchBendEvent((ushort)0x3000); outputDevice.SendEvent(pitchBendDown); // 恢复原音高的话,发送0x4000的弯音值 var resetPitch = new PitchBendEvent(0x4000); outputDevice.SendEvent(resetPitch);
注意:这种方法依赖你的MIDI合成器支持弯音轮功能,如果需要更大的弯音范围,还得先发送RPN消息调整弯音跨度。
内容的提问来源于stack exchange,提问作者Alex
相关产品推荐
相关产品推荐

