MIDI 音符长度不正确

Incorrect MIDI note length

我正在尝试用 C# 编写 MIDI 文件。我正在使用 Sanford MIDI 工具包。以下是我用来编写 NoteOnNoteOff 事件的代码片段。

private static void InsertNoteOn(Track t, int pitch, int velocity, int position, int duration, int channel)
{
    ChannelMessageBuilder builder = new ChannelMessageBuilder();
    builder.Command = ChannelCommand.NoteOn;
    builder.Data1 = pitch;
    builder.Data2 = velocity;
    builder.MidiChannel = channel;
    builder.Build();
    t.Insert(position, builder.Result);
}

private static void InsertNoteOff(Track t, int pitch, int velocity, int position, int duration, int channel)
{
    ChannelMessageBuilder builder = new ChannelMessageBuilder();
    builder.Command = ChannelCommand.NoteOff;
    builder.Data1 = pitch;
    builder.Data2 = velocity;
    builder.MidiChannel = channel;
    builder.Build();
    t.Insert((position + duration), builder.Result);
}

首先我为轨道的所有音符插入所有 NoteOn 个事件,然后我插入轨道的所有 NoteOff 个事件。

这种方法在大多数情况下都有效。但是,有时会错误地呈现生成的 MIDI 文件。当两个相同音高的音符相继写入时,有时会出现此问题。第一个音符的长度将等于两个音符的长度,第二个音符的长度为零。

我的假设是第一个音符的 NoteOff 事件被解释为第二个音符的 NoteOff ,反之亦然。

我尝试了以下变体:

  1. 在音轨中按时间顺序添加每个单独的音符及其 NoteOnNoteOff 事件
    1. 如果音符位置相同,高音与低音
    2. 如果音符位置相同,则底音高到高音
  2. 按时间顺序添加所有 NoteOn 个事件,然后按时间顺序添加所有 NoteOff 个事件
  3. 按时间顺序添加所有 NoteOff 个事件,然后按时间顺序添加所有 NoteOn 个事件
  4. 尽可能减少所有音符的长度

只有最后一种方法有效,但注释长度较短,无法解决问题。

有解决办法吗? NoteOnNoteOff 事件在赛道中是否有特定的顺序? track插入方法的调用有没有特定的顺序?

EDIT: 问题发生在以下情况:

为了可见性,较高音符已从 C 移至 C#。这些应该是两个相同长度的音符,但一个是用两个音符的长度渲染的,另一个是零长度。

在 MIDI 文件中,多个事件可能具有相同的时间戳。在这种情况下,它们将按照写入文件的顺序通过网络发送。

Sanford MIDI 工具包仅使用时间戳来指定事件的位置,没有记录如何处理具有相同时间戳的多个事件。

为了确保您的音符关闭事件发生在音符开启事件之前,您必须使用不同的时间戳,即减少音符的长度。 (要降低实际差异,请提高时间戳分辨率。)

我通过在将所有事件插入轨道之前对所有事件进行排序解决了这个问题。我用了下面的方法。

private static void InsertNote(int pitch, int velocity, int position, int duration, int channel, ref List<Tuple<int, bool, ChannelMessage>> messages)
{
    ChannelMessageBuilder builder = new ChannelMessageBuilder();
    builder.Command = ChannelCommand.NoteOn;
    builder.Data1 = pitch;
    builder.Data2 = velocity;
    builder.MidiChannel = channel;
    builder.Build();
    messages.Add(new Tuple<int, bool, ChannelMessage>(position, true, builder.Result));
    builder.Command = ChannelCommand.NoteOff;
    builder.Data1 = pitch;
    builder.Data2 = velocity;
    builder.MidiChannel = channel;
    builder.Build();
    messages.Add(new Tuple<int, bool, ChannelMessage>(position + duration, false, builder.Result));
}

使用方法如下。

List<Tuple<int, bool, ChannelMessage>> messages = new List<Tuple<int, bool, ChannelMessage>>();
foreach (var n in track.Notes)
    InsertNote(n.Pitch, n.Velocity, (int)(n.Position * LENGTH_MULTIPLIER), (int)(n.Length * LENGTH_MULTIPLIER), 0, ref messages);
messages = messages.OrderBy(x => x.Item1).ThenBy(x => x.Item2).ToList();
foreach (var x in messages)
    t.Insert(x.Item1, x.Item3);