【问题标题】:Calculate accurate BPM from MIDI clock in ObjC with CoreMIDI使用 CoreMIDI 从 ObjC 中的 MIDI 时钟计算准确的 BPM
【发布时间】:2012-11-26 10:17:57
【问题描述】:

我在从接收的 MIDI 时钟计算准确的 BPM 时遇到了一些问题(在我的测试中使用 Ableton Live 来发送 MIDI 时钟)。

我正在使用 Pete Goodliffe 的 CoreMIDI 和 PGMidi。

在 PGMidi 库中,有一个在接收 MIDI 消息时调用的方法。从文档来看,这是从高优先级后台线程发生的。

这是我当前计算 BPM 的实现

double BPM;
double currentClockInterval;
uint64_t startClockTime;

- (void) midiSource:(PGMidiSource*)input midiReceived:(const MIDIPacketList *)packetList
{
    [self onTick:nil];

    MIDIPacket  *packet = MIDIPacketListInit((MIDIPacketList*)packetList);
    int statusByte = packet->data[0];
    int status = statusByte >= 0xf0 ? statusByte : statusByte >> 4 << 4;

    switch (status) {
        case 0xb0: //cc
                   //NSLog(@"CC working!");
            break;
        case 0x90: // Note on, etc...
                   //NSLog(@"Note on/off working!");
            break;
        case 0xf8: // Clock tick


            if (startClockTime != 0)
            {
                uint64_t currentClockTime = mach_absolute_time();
                currentClockInterval = convertTimeInMilliseconds(currentClockTime - startClockTime);

                BPM = (1000 / currentClockInterval / 24) * 60;

                dispatch_async(dispatch_get_main_queue(), ^{
                    NSLog(@"BPM: %f",BPM);
                });

            }

            startClockTime = mach_absolute_time();

            break;
    }
}

uint64_t convertTimeInMilliseconds(uint64_t time)
{
    const int64_t kOneMillion = 1000 * 1000;
    static mach_timebase_info_data_t s_timebase_info;

    if (s_timebase_info.denom == 0) {
        (void) mach_timebase_info(&s_timebase_info);
    }

    // mach_absolute_time() returns billionth of seconds,
    // so divide by one million to get milliseconds
    return (uint64_t)((time * s_timebase_info.numer) / (kOneMillion * s_timebase_info.denom));
}

但由于某些原因,计算出的 BPM 并不准确。当我从 Ableton Live 发送低于 70 的 BPM 时,这很好,但我发送的 BPM 越高,准确度越低,例如:

  • 在 Live 中设置 69 BPM 给我 69.44444
  • 100 -> 104.16666666
  • 150 -> 156.250
  • 255 -> 277.7777777

有人可以帮我解决这个问题吗?我相信我可能没有使用一个好的策略来计算 BPM。我首先使用 mach_absolute_time() 计算每个 midi 时钟之间经过的时间。

感谢您的帮助!

更新

按照 Kurt 的回答,这是一个适用于 iOS 的更准确的例程(因为我没有使用仅在 OSX 上可用的 CoreAudio/HostTime.h)

double currentClockTime;
double previousClockTime;

- (void) midiSource:(PGMidiSource*)input midiReceived:(const MIDIPacketList *)packetList
{
    MIDIPacket *packet = (MIDIPacket*)&packetList->packet[0];
    for (int i = 0; i < packetList->numPackets; ++i)
    {

        int statusByte = packet->data[0];
        int status = statusByte >= 0xf0 ? statusByte : statusByte & 0xF0;

        if(status == 0xf8)
        {
            previousClockTime = currentClockTime;
            currentClockTime = packet->timeStamp;

            if(previousClockTime > 0 && currentClockTime > 0)
            {
                double intervalInNanoseconds = convertTimeInNanoseconds(currentClockTime-previousClockTime);
                BPM = (1000000 / intervalInNanoseconds / 24) * 60;
            }
        }

        packet = MIDIPacketNext(packet);
    }

    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"BPM: %f",BPM);
    });
}

uint64_t convertTimeInNanoseconds(uint64_t time)
{
    const int64_t kOneThousand = 1000;
    static mach_timebase_info_data_t s_timebase_info;

    if (s_timebase_info.denom == 0)
    {
        (void) mach_timebase_info(&s_timebase_info);
    }

    // mach_absolute_time() returns billionth of seconds,
    // so divide by one thousand to get nanoseconds
    return (uint64_t)((time * s_timebase_info.numer) / (kOneThousand * s_timebase_info.denom));
}

正如你所看到的,我现在依赖的是 MidiPacket 时间戳而不是 mach_absolute_time(),它可能会以不固定的数量关闭。 此外,我现在使用纳秒来提高精度,而不是使用毫秒来计算 BPM。

通过这个例程,我现在得到了更准确的结果但是它仍然会偏离低于 150 的 BPM 的一小部分,并且在非常高的 BPM(例如 > 400 BPM)上可能会偏离高达 10 BPM ):

  • 将主机设置为 100 BPM 给我 100.401606
  • 150 BPM -> 149.700599 ~ 150.602410
  • 255 BPM -> 255.102041 ~ 257.731959
  • 411 BPM -> 409.836066 ~ 416.666667

是否还有其他需要考虑的因素来获得更准确的结果?

感谢您的帮助库尔特!很有帮助!

更新 2

我 fork PGMidi 并添加了一些功能,例如 BPM 计算和量化。 回购在这里https://github.com/yderidde/PGMidi

我相信它可以优化得更准确。量化程序也不完美...... 因此,如果有人在我的代码中发现一些错误或有建议让整个事情更稳定/准确,请告诉我!!

【问题讨论】:

    标签: objective-c ios midi coremidi


    【解决方案1】:

    这里有一些错误,有些比其他的更重要。

    最重要的是:您正在使用整数毫秒,这不足以获得准确的节拍/分钟。让我们以 120 次/分钟为例。在 120 次/分钟和 24 个时钟/次的情况下,每个时钟在 20.833 毫秒内到达。由于您正在计算整数毫秒,因此这似乎是 20 或 21 毫秒。当您进行数学运算(加倍!)以恢复 BPM 时,您可以得到 125 次/分钟或 119.0476 次/分钟。两者都不是您所期望的。

    如果您使用整数微秒或纳秒进行数学运算,您将获得更准确的值。我建议使用在&lt;CoreAudio/HostTime.h&gt; 中定义的AudioConvertHostTimeToNanos()MIDITimeStamp 转换为整数纳秒,然后转换为double 并从那里开始。你不应该自己使用mach_timebase_info

    还有:

    • MIDIPackets 有一个 timeStamp 值,用于标记收到它们的时间。 CoreAudio 费了很大的力气才给你那个时间戳,所以用它吧!

      不要依赖于对mach_absolute_time() 的调用,这将在以后的时间不一致,具体取决于您无法控制的许多因素。

    • 不要打电话给MIDIPacketListInit

      要遍历MIDIPacketList 中的每个MIDIPacket,请使用此代码,直接来自MIDIServices.h

      MIDIPacket *packet = &packetList->packet[0];
      for (int i = 0; i < packetList->numPackets; ++i) {
          /* your code to use the packet goes here */
          packet = MIDIPacketNext(packet);
      }
      
    • statusByte &gt;&gt; 4 &lt;&lt; 4 看着就疼。你的意思是statusByte &amp; 0xF0

    【讨论】:

    • 谢谢库尔特!我正在调查这个。我会告诉你它在实施时是否有效。一件事...... 在 iOS 中不存在。所以我相信我除了拥有自己的毫秒或纳秒转换器之外别无选择。
    • 我根据您的建议更新了我的问题并实施了!它仍然关闭,但它已经更加准确。也许还有其他可以做的优化?
    • 啊哈,我没有意识到 iOS 上不存在。您可以从this questionCAHostTimeBase in the CoreAudio sample code 中的ConvertToNanos 借用工作代码。我什至不相信自己能正确地计算出数学......
    • 我没有任何特别的建议可以让它更准确,但没有看到它的实际效果。这些值是否始终以相同的量偏离,或者是否有任何抖动? MIDI 不是世界上最先进的技术,你可能无法做得更好;如果您想要一个稳定的整数来向用户展示,您可能需要随着时间的推移进行一些平均/平滑处理。
    • 这完全取决于主机 BPM。在所有情况下,它永远不会是完全相同的 BPM。例如,当我的主机设置为396.00 BPM我得到下面这个控制台日志:396.825397 396.825397 396.825397 396.825397 396.825397 396.825397 396.825397 396.825397 396.825397 396.825397 390.625000 396.825397 396.825397 396.825397 396.825397 396.825397 396.825397 390.625000 396.825397 396.825397 396.825397 396.825397 396.825397 396.825397 390.625000 396.825397 396.825397 396.825397 396.825397 396.825397 396.825397 跨度>
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-09-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多