【问题标题】:Firing events at microsecond resolution for midi sequencer为 midi 音序器以微秒分辨率触发事件
【发布时间】:2011-03-25 00:39:43
【问题描述】:

有没有办法在 C# 中以几微秒的分辨率触发事件?

我正在构建一个 MIDI 音序器,它需要在每个 MIDI 滴答声中触发一个事件,然后该事件将播放当时注册的任何音符。

在每分钟 120 拍和 120 ppqn(每拍/四分音符的脉冲数)的分辨率下,该事件应每 4.16666 毫秒触发一次。现代音序器具有更高的分辨率,例如 768ppqn,这需要每 651 微秒触发一次该事件。

我发现的短时间事件的最佳分辨率是 1 毫秒。我该如何超越?

这个问题肯定已经被任何 C# MIDI 音序器或 MIDI 文件播放器解决了。 也许我只是没有从正确的角度看待问题。

感谢您的帮助。

【问题讨论】:

  • 我不遵循你的数学。如果 120ppqn=41.6666ms,肯定是 768ppqn!=651microsec,但是 768ppqn=1000*41.6666*120/768=6510 microsec?
  • 120 bpm = 每 500 毫秒 1 拍,每拍 120 滴答 = 500 毫秒/120 = 4.166 毫秒 & 每拍 768 滴答 = 500 毫秒/768 = 0.651 毫秒 我的第一个数字确实有错字,我现在会纠正它。谢谢。
  • 感谢大家抽出宝贵时间帮助我。每个答案都教会了我一些有趣的东西。解决方案确实是放弃毫秒和微秒,而是使用样本。由于 44100Khz 音频的 1 个样本等于 1/44100 秒,因此可以达到 22.67 微秒的精度。
  • @Brice:我知道,这个问题很老,但你找到解决方案了吗?它是什么?我的问题基本上是一样的。例如,120 BPM 的 1/64 音符事件应该每 31.25 毫秒播放一次,或者每 62.5 毫秒播放 1/32 音符。你如何测量半毫秒和四分之一毫秒?你用的是计时器还是秒表?您如何测量样品?

标签: c# timer precision midi


【解决方案1】:

而不是使用计时器

使用stopwatch

例如,10x 1 秒

    static void Main(string[] args)
    {
        Stopwatch masterSW;
        Stopwatch sw=null;
        int count;

        sw = Stopwatch.StartNew();
        sw.Reset();

        for (int i = 0; i < 10; i++)
        {
            count = 0;
            masterSW = Stopwatch.StartNew();
            while (count!=1536) //1537*651 microsecond is about a second (1.0005870 second)
            {
                if (!sw.IsRunning)
                    sw.Start();

                if (sw.Elapsed.Ticks >= 6510)
                {
                    count++;
                    sw.Reset();
                }
            }

            Debug.WriteLine("Ticks: " + masterSW.Elapsed.Ticks.ToString());
        }
    }

将输出:

滴答声:10005392(即 1.0005392 秒)
蜱:10004792
蜱:10004376
蜱:10005408
滴答声:10004398
蜱:10004426
滴答声:10004268
蜱:10004427
蜱:10005161
滴答声:10004306

看起来还不错

【讨论】:

  • 谢谢。我曾尝试使用秒表,但据我所知,它只允许检查 stopwatch.start 和 stopwatch.stop 之间的经过时间,它不会定期触发事件
  • 他在相当固定的时间间隔执行count++。只要机器上绝对没有其他任何东西在运行,它就可以工作。
【解决方案2】:

我认为您不太可能从计时器中获得完全正确的分辨率。更好的方法是使用 1ms 精确计时器,并在它触发时检查哪些 MIDI 事件处于未决状态并触发它们。

因此,MIDI 事件进入排序队列,您查看第一个事件,并将计时器设置为尽可能接近该时间触发。当计时器触发时,消耗队列中所有已经过去的事件,直到遇到未来的事件。计算此事件的时间。重新安排计时器。

当然,如果您要输出到声卡,则方法根本不同,您应该计算所有时间的样本。

【讨论】:

    【解决方案3】:

    大多数 MIDI 音序器/MIDI 播放器要么将大段时间转换为波形(用于通过计算机扬声器播放),要么采用大量 MIDI 指令(用于连接到 MIDI 端口的外部设备)。无论哪种方式,都会将数据块复制到声卡,而声卡会负责准确的计时。

    您可能想查看多媒体控制 API。

    this post over at the Microsoft discussion forum

    【讨论】:

      【解决方案4】:

      在 .NET 中不可能以微秒为间隔准确触发事件。

      事实上,由于 Windows 本身不是实时操作系统,因此在用户模式软件中以 100% 准确度到特定微秒执行任何操作几乎是不可能的。

      有关为什么这如此困难的更多信息,请参阅 MSDN 杂志文章:Implement a Continuously Updating, High-Resolution Time Provider for Windows。虽然它谈论的是 Windows NT,但这通常仍然适用于更高版本的 Windows。

      这篇文章的结论总结的很好:

      如果你现在认为你可以获得 系统时间几乎 这里的任意精度,只是一个 轻微警告:不要忘记 多任务处理的抢占性 Windows NT 等系统。在最好的 情况下,您将获得的时间戳已关闭 仅在阅读所需的时间 性能计数器并转换它 读入绝对时间。在里面 最坏的情况下,经过的时间可能 很容易在几十个数量级 毫秒。

      虽然这可能 表明你经历了所有这些 一劳永逸,您放心 没有。即使执行对 Win32 API GetSystemTimeAsFileTime(或 Unix 下的 gettimeofday) 受制于 相同的条件,所以你是 实际上并没有比这更糟。在 大多数情况下,您将拥有 好结果。只是不表演 任何需要实时的东西 基于时间的可预测性 Windows NT 中的标记。

      【讨论】:

      • 应该说“在用户模式下”,而不是“在软件中”,因为内核驱动程序也是软件,当然能够达到亚微秒级的计时/延迟。
      猜你喜欢
      • 2020-05-26
      • 1970-01-01
      • 2012-11-12
      • 2018-10-29
      • 2016-07-11
      • 2010-09-05
      • 1970-01-01
      • 1970-01-01
      • 2015-08-07
      相关资源
      最近更新 更多