5

受信しているMIDIクロックから正確なBPMを計算するのに問題があります(MIDIクロックを送信するためのテストでAbleton Liveを使用しています)。

ピート・グッドリフのCoreMIDIとPGMidiを使用しています。

PGMidi libには、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が高いほど、精度が低くなります。たとえば、次のようになります。

  • ライブで69BPMを設定すると、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));
}

ご覧のとおり、私は現在、mach_absolute_time()ではなく、MidiPacketのtimeStampに依存しています。これは、一定の量だけずれている可能性があります。また、BPMの計算にミリ秒を使用する代わりに、精度を高めるためにナノ秒を使用しています。

このルーチンを使用すると、はるかに正確なものが得られますが、それでも150未満のBPMの何分の1かオフであり、非常に高いBPM(たとえば> 400 BPM)では最大10BPMオフになる可能性があります

  • ホストを100BPMに設定すると、100.401606になります
  • 150 BPM-> 149.700599〜150.602410
  • 255 BPM-> 255.102041〜257.731959
  • 411 BPM-> 409.836066〜416.666667

さらに正確なものを得るために考慮すべき他の何かがありますか?

助けてくれてありがとうカート!非常に役立ちます !

更新2

PGMidiをフォークし、BPM計算や量子化などの機能を追加しました。リポジトリはこちらhttps://github.com/yderidde/PGMidi

より正確になるように最適化できると確信しています。また、クオンタイズルーチンは完璧ではありません...ですから、誰かが私のコードに間違いを見つけたり、全体をより安定した/正確にするための提案があれば、私に知らせてください!!

4

1 に答える 1

4

ここにはいくつかの間違いがあり、いくつかは他よりも重要です。

最も重要なこと: 整数のミリ秒で作業しているため、正確なビート/分を得るには十分な精度ではありません。例として 120 拍/分を使用してみましょう。120 ビート/分、24 クロック/ビートの場合、各クロックは 20.833 ミリ秒で到着します。整数ミリ秒を計算しているため、20 ミリ秒または 21 ミリ秒のいずれかになります。BPM に戻すために (2 倍で!) 計算すると、125 ビート/分または 119.0476 ビート/分が得られます。どちらもあなたが期待するものではありません。

整数のマイクロ秒またはナノ秒で計算すると、より正確な値が得られます。AudioConvertHostTimeToNanos()で定義されている を使用して<CoreAudio/HostTime.h>、 aMIDITimeStampからナノ秒の整数に変換し、doubleそこから変換して移動することをお勧めします。自分を使わなくていいmach_timebase_info

また:

  • MIDIPackets には、timeStamp受信した時点を示す値があります。CoreAudio は、そのタイムスタンプを取得するために多大な労力を費やすので、それを使用してください!

    への呼び出しに依存しないmach_absolute_time()でください。制御できない多くの要因によって、一貫性のない時間が遅れます。

  • 電話しないでくださいMIDIPacketListInit

    のそれぞれを反復処理するMIDIPacketにはMIDIPacketList、次のコードを から直接使用します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 >> 4 << 4見て痛い。つまりstatusByte & 0xF0

于 2012-11-27T06:51:12.167 に答える