【发布时间】: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