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

Posted

技术标签:

【中文标题】使用 NAudio 从一段 MIDI 文件中读取 MIDI 事件、消息【英文标题】:Reading MIDI Events, Messages from a chunk of a midi file using NAudio 【发布时间】:2020-09-06 09:52:08 【问题描述】:

我是 MIDI 方面的新手,所以请不要对我残忍 :) 我有一个 Yamaha midi 文件,其中包含一些部分,例如 Midi Header 部分、CASM 部分、OTS 部分、MDB 部分和 MH 部分。 我想关注一下 OTS 部分。 OTS 部分包含 ID = 4 字节、数据长度 = 4 字节和数据。数据是一个 MIDI 文件结构块,但它不包含音符,只有使用的通道等设置,以及每个通道的设置,如使用语音的 MSB-LSB-PC、音量、和声等。 问题是如何检索使用的通道,如何检索使用的 MSB-LSB-PC 对语音/鼓? NAudio 可以这样做还是我必须使用其他 MIDI 包工具?

编辑:

OTS 数据将包含至少一个 OTS 轨道。每个 OTS Track 的结构如下:

byte[0]->byte[3] = 'MTrk' (midi track header of SMF)
byte[4]->byte[7] = 256*256*256*byte[4] + 256*256*byte[5] + 256*byte[6] + byte[7] -> Data length on OTS Track.
byte[8]->byte[n] = SMF data of OTS Track.

因此,OTS 数据将至少包含一次这种结构。我将能够读取每个 OTS Track,但我不知道哪些 C# 指令可以从那些 OTS Track Data SMF 数据中获取那些 MSB-LSB-PC 信息...

【问题讨论】:

我是 DryWetMIDI 库的作者,它允许您读取 MIDI 数据,包括自定义块(如 OTS)。但当然,您需要自己编写代码来读取您的自定义块。 Here 定义自定义块的示例,以便您可以阅读它。另外,您能否提供一些带有 OTS 块的示例文件? 嗨@Maxim。我已经编辑了我的问题。请告诉我如何使用您的库来读取 SMF 中频道的 MSB-LSB-PC? 为了帮助你,我需要知道 OTS 块的确切格式。因为它是一个 MIDI 块,所以我知道它的标题。但是 chink 内容是什么? “OTS Track的SMF数据”是什么?我也不知道什么是“MSB-LSB-PC”。除了格式信息,您可以提供带有 OTS 块的文件吗?你也可以create new issue on GitHub. wierzba.homepage.t-online.de/stylefiles.htm 这是一个文件,它将详细解释样式文件包含的内容。我需要知道(提取)来自 OTSc 的数据。 mediafire.com/file/d4ryg390q5qdkqu/… 这是一个包含 OTSc 块的 Yamaha Style 文件。我必须从那里提取所有 OTS 音轨,并为每个 OTS 音轨提取一些信息,例如程序更改(包括用于选择正确语音的 MSB 和 LSB)音量、和声和其他数据... 现在你应该自己做很多工作。 但是我做了快速测试,我会尝试在 DryWetMIDI 中提供一个 API,让您可以轻松读取 OTS 块。一旦我实现了那个 API,我会发布一个答案。我想我今天或明天会这样做。感谢您分享您的案例! 【参考方案1】:

我可以建议您使用我的 DryWetMIDI 库。库文档中有一篇文章描述了如何定义自定义块类并读取其数据:Custom chunks。

至于 OTS 块,从您提供的链接中,我看到 OTS 块的数据实际上是没有标题块的 MIDI 文件。因此我们可以将其内容读取为 MIDI 文件并获取文件的音轨块。

让我们定义我们的块类:

public sealed class OtsChunk : MidiChunk

    public const string Id = "OTSc";

    public OtsChunk()
        : base(Id)
    
    

    public IEnumerable<TrackChunk> TrackChunks  get; private set; 

    protected override void ReadContent(MidiReader reader, ReadingSettings settings, uint size)
    
        var data = reader.ReadBytes((int)size);

        using (var memoryStream = new MemoryStream(data))
        
            var midiFile = MidiFile.Read(memoryStream, new ReadingSettings
            
                NoHeaderChunkPolicy = NoHeaderChunkPolicy.Ignore
            );

            TrackChunks = midiFile.GetTrackChunks().ToArray();
        
    

    public override MidiChunk Clone()
    
        throw new NotImplementedException();
    

    protected override uint GetContentSize(WritingSettings settings)
    
        throw new NotImplementedException();
    

    protected override void WriteContent(MidiWriter writer, WritingSettings settings)
    
        throw new NotImplementedException();
    

我们不会实现CloneGetContentSizeWriteContent,因为您只对阅读感兴趣。 (如果您希望能够手动创建此类块并将其写入 MIDI 文件,则还需要实现最后两个方法。)

现在我们可以读取 Yamaha MIDI 文件并获取 OTS 块:

var midiFile = MidiFile.Read("LionelRichie Hello_Amkey_TY.sty", new ReadingSettings

    CustomChunkTypes = new ChunkTypesCollection
    
         typeof(OtsChunk), OtsChunk.Id 
    
);

var otsChunk = midiFile.Chunks.OfType<OtsChunk>().FirstOrDefault();

然后您可以从otsChunk.TrackChunks 的每个轨道块中获取常规 MIDI 事件。例如,

var firstTrackChunkSysExEvents = otsChunk.TrackChunks.First().Events.OfType<NormalSysExEvent>();
var firstSysExEvent = firstTrackChunkSysExEvents.First();
var firstData = firstSysExEvent.Data;

firstData 将在 OTS 块的第一个轨道块中包含第一个 sys ex 事件的字节。请注意,数据不会包含第一个F0 字节。

【讨论】:

谢谢@Maxim。当然,我会试一试。 很抱歉在这里发帖,但我无权发布其他问题。如何在CustomChunk 中替换TrackChunk?。所以,正如你所看到的,在那个 OTS 中,我有 4 首曲目,我想替换它们,每首曲目都有另一个 TrackChunk,甚至是一个空的 TruckChunk。谢谢。 你能实现类似ReplaceChunk(ChunkType oldChunk, ChunkType newChunk)的方法吗?能够加载 Midi 文件并运行这个小命令。 MidiChunk 会根据新数据自动改变它的大小……你怎么看?此外,RemoveChunk()、EmptyChunk()、RemoveTrack()、EmptyTrack() 可能很有用! 已通过电子邮件回复。 谢谢马克西姆。 Github 进度……你是最棒的!

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

NAudio 和 Midi 文件读取

如何使用 NAudio 读取 MIDI 音符?

NAudio - 从 Stream 播放 MIDI

使用 NAudio 在 MIDI 文件中设置 MIDI 速度

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

NAudio.Midi 延迟与 MidiIn