【问题标题】:Incorrect MIDI note lengthMIDI 音符长度不正确
【发布时间】:2016-07-08 00:00:25
【问题描述】:

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

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 事件在赛道上是否有特定的顺序?是否有特定的轨道插入方法调用顺序?

编辑:在以下情况下会出现问题:

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

【问题讨论】:

  • 如果不按时间顺序添加消息,您很有可能会在库中出错。只需自己先对它们进行排序。
  • 我试过了,还是失败了。是否有明确的排序顺序?在同一时间点发生的消息会出现此问题。
  • 渲染不正确是什么意思?使用 MIDI 文件,事件的顺序无论是打开还是关闭都不是问题,但播放当然会像一个音符一样永远播放。 (或者直到下一个音符关闭事件相同的可能不是以后的措施)
  • 所以问题是on1 - - on2 - - off2 - - off1。并且这种行为是设计良好的。注意 1 将播放到 off2。这是按下钢琴键并再次按下而不释放它,这是不可能的。
  • 天哪。但是,您应该以确切的顺序提供您的开启和关闭事件,因为软件无法确定在同一时间戳上发生的所需事件顺序

标签: c# midi


【解决方案1】:

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

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

为确保您的 note-off 事件发生在 note-on 事件之前,您必须使用不同的时间戳,即减少音符的长度。 (要降低实际差异,请增加时间戳分辨率。)

【讨论】:

  • 他试图完成的事情其实是非常明智的。在同一时刻关闭音符并再次打开它。问题在于他插入事件的方式,首先是所有关于事件的注释,然后是所有关闭事件,在这种情况下,在同一时间戳中,注释首先出现。他应该能够在不改变音符长度的情况下实现这一点
  • @OguzOzgul 不能保证具有相同时间戳的事件在插入时具有相同的顺序。事实上,Sanford MIDI 工具包使用插入排序。
【解决方案2】:

我通过在将所有事件插入轨道之前对其进行排序来解决问题。我使用了以下方法。

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);

【讨论】:

  • 你能详细解释一下这个和你的解决方案吗?我在 node.js 中有同样的问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-11-29
  • 1970-01-01
  • 2017-05-08
  • 2010-11-27
相关资源
最近更新 更多