MIDI 音序器以微秒分辨率触发事件

发布于 2024-09-12 22:12:12 字数 328 浏览 6 评论 0原文

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

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

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

我发现短时事件的最佳分辨率是 1 毫秒。我怎样才能超越这个范围?

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

感谢您的帮助。

Is there a way to fire events in C# at a resolution of a few microseconds?

I am building a MIDI sequencer, and it requires an event to be fired every MIDI tick, which will then play any note registered at that time.

At 120 beats per minute and at a resolution of 120 ppqn (pulses per beat/quarter note), that event should fire every 4.16666 milliseconds. Modern sequencers have higher resolutions such as 768ppqn which would require that event to be fired every 651 microseconds.

The best resolution for short-timed events I have found is of 1 millisecond. How can I go beyond that?

This problem must have already been solved by any C# MIDI sequencer or MIDI file player.
Maybe am I just not looking at the problem through the right angle.

Thank you for your help.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

季末如歌 2024-09-19 22:12:12

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

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

请参阅 Microsoft 论坛上的此帖子

Most midi sequencers/midi players will either convert large blocks of time to waveform (for playing through computer speakers) or take a large block of MIDI instructions (for an external device attached to a MIDI port). Either way, a block of data is copied to the sound card, and the sound card takes care of exact timing.

You might want to look at the Multimedia Control APIs.

See this post over at the Microsoft discussion forum

捶死心动 2024-09-19 22:12:12

我认为你不太可能从计时器中获得完全正确的分辨率。更好的方法是使用 1 毫秒精确的计时器,当它触发时,检查哪些 MIDI 事件正在等待并触发它们。

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

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

I think you are unlikely to get exactly the correct resolution from a timer. A better approach would be to use the 1ms accurate timer, and when it fires, to check which MIDI events are pending and to fire them.

So, the MIDI events go in a sorted queue, you peek the first one, and set the timer to fire as close as possible to that time. When the timer fires, consume all events from the queue that have elapsed, until you encounter a future event. Calculate time to this event. Reschedule timer.

Of course, if you are outputting to your soundcard, the approach is fundamentally different, and you should be counting samples for all your timings.

烂人 2024-09-19 22:12:12

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

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

有关为什么这如此困难的更多信息,请参阅 MSDN 杂志文章:实施连续正在更新适用于 Windows 的高分辨率时间提供程序。虽然它谈论的是 Windows NT,但这通常仍然适用于更高版本的 Windows。

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

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

虽然这可能
表明你经历了这一切
不劳而获,请放心
没有。甚至执行对
Win32 API GetSystemTimeAsFileTime(或
gettimeofday 在 Unix 下)受
相同的条件,所以你是
实际上做的并不比这更糟糕。在
大多数情况下,你都会有
良好的结果。只是不执行
任何需要实时的东西
基于时间的可预测性
Windows NT 中的标记。

It is not possible to have events accurately fired on microsecond intervals in .NET.

In fact because Windows itself is not a real time OS, performing anything with 100% accuracy to certain microseconds, in user mode software, is pretty much impossible.

For more information on why this is so difficult see the MSDN magazine article: Implement a Continuously Updating, High-Resolution Time Provider for Windows. While it talks about Windows NT, this still generally applies to later versions of Windows.

The conclusion of this article sums it up well:

If you now think that you can obtain
the system time with an almost
arbitrary precision here, just a
slight warning: don't forget the
preemptiveness of a multitasking
system such as Windows NT. In the best
case, the time stamp you'll get is off
by only the time it takes to read the
performance counter and transform this
reading into an absolute time. In the
worst cases, the time elapsed could
easily be in the order of tens of
milliseconds.

Although this might
indicate you went through all of this
for nothing, rest assured that you
didn't. Even executing the call to the
Win32 API GetSystemTimeAsFileTime (or
gettimeofday under Unix) is subject to
the same conditions, so you are
actually doing no worse than that. In
a majority of the cases, you will have
good results. Just don't perform
anything requiring real-time
predictability on the basis of time
stamps in Windows NT.

淡看悲欢离合 2024-09-19 22:12:12

而不是使用计时器,

请使用秒表

示例,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

看起来还不错

instead of using timer

use stopwatch

example, for 10x 1 second

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

will output:

Ticks: 10005392 (which is 1.0005392 second)
Ticks: 10004792
Ticks: 10004376
Ticks: 10005408
Ticks: 10004398
Ticks: 10004426
Ticks: 10004268
Ticks: 10004427
Ticks: 10005161
Ticks: 10004306

which seem kind of ok

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文