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