Macos 使用CoreMIDI在mac上发送稳定的midi时钟
我正在做一个小程序,将一个抖动的MIDI时钟转换成稳定的节拍。抖动的MIDI时钟产生可怕的颤音 其想法是“监听”传入的midi时钟,并在确定节奏后,向虚拟IAC设备发送一个稳定的midi时钟,以便我可以将我的DAW(NI机器)同步到同一IAC设备。输入MIDI来自Korg Electribe,所以我只能使用MIDI电缆。我使用Komplete Audio 6来接收MIDI时钟 第一部分(听并确定节奏)已经讲过了,但现在我必须为这个节奏生成一个稳定的时钟 我尝试使用高优先级线程发送midi时钟。下面的测试程序给了我一个介于119.8和120.2之间的节奏抖动 我在这个例行程序中是做错了什么,还是应该使用另一种策略? 非常感谢您的帮助 问候,, 抢劫 更新 想出一个有效的策略。下面的代码在我的系统上给出了一个完美的结果。我已经在乐队的演唱会上用过了,效果很好 我的解决方案是:Macos 使用CoreMIDI在mac上发送稳定的midi时钟,macos,coremidi,midi-interface,Macos,Coremidi,Midi Interface,我正在做一个小程序,将一个抖动的MIDI时钟转换成稳定的节拍。抖动的MIDI时钟产生可怕的颤音 其想法是“监听”传入的midi时钟,并在确定节奏后,向虚拟IAC设备发送一个稳定的midi时钟,以便我可以将我的DAW(NI机器)同步到同一IAC设备。输入MIDI来自Korg Electribe,所以我只能使用MIDI电缆。我使用Komplete Audio 6来接收MIDI时钟 第一部分(听并确定节奏)已经讲过了,但现在我必须为这个节奏生成一个稳定的时钟 我尝试使用高优先级线程发送midi时钟。下
dispatch_source_t CreateDispatchTimer(uint64_t interval,
uint64_t leeway,
dispatch_queue_t queue,
dispatch_block_t block)
{
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER,
0, 0, queue);
if (timer)
{
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, interval, leeway);
dispatch_source_set_event_handler(timer, block);
dispatch_resume(timer);
}
return timer;
}
- (void) timerTempo:(double) tempo{
if (ignoreTempoChange) return; // ignoreTempoChange is set when a MIDI start is received
_inTempo = tempo;
if (aTimer)
{
nTicks = ticks_per_second / (tempo * 24 / 60); //number of ticks for one beat.
nTicks = nTicks/1000;
nTicks = nTicks*1000;
dispatch_source_set_timer(aTimer, DISPATCH_TIME_NOW, nTicks * 24, 0);
}
}
- (void) startTimer:(double) tempo{
_inTempo = tempo;
mach_timebase_info_data_t mach_timebase_info_data_t;
mach_timebase_info( &mach_timebase_info_data_t ); //denum and numer are always 1 on my system???
ticks_per_second = mach_timebase_info_data_t.denom * NSEC_PER_SEC / mach_timebase_info_data_t.numer;
nTicks = ticks_per_second / (tempo * 24 / 60); //number of ticks for one beat.
nTicks = nTicks/1000;
nTicks = nTicks*1000; // rounding the nTicks to microseconds was THE trick to get a rock solid clock in NI Maschine
clocktTimeStamp = mach_absolute_time();
dispatch_queue_t q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
aTimer = CreateDispatchTimer(nTicks * 24,
0,
q,
^{
const int packetListSize = sizeof(uint32)+ (25 *sizeof(MIDIPacket));
MIDIPacketList *packetList= malloc(packetListSize);
MIDIPacket *packet = MIDIPacketListInit( packetList );
Byte clock = 0xF8;
for( int i = 0; i < 24; i++ )
{
packet = MIDIPacketListAdd( packetList, packetListSize, packet, clocktTimeStamp, 1, &clock );
clocktTimeStamp+= nTicks;
}
MIDISend(outPort, IAC, packetList );
free(packetList);
});
timerStarted = true;
}
dispatch\u source\u t CreateDispatchTimer(uint64\u t间隔,
uint64_t回旋余地,
调度队列,
调度(分块)
{
调度源定时器=调度源定时器,
0,0,队列);
中频(定时器)
{
调度源设置定时器(定时器、调度时间、间隔、回旋余地);
调度\源\集\事件\处理程序(计时器、块);
调度恢复(计时器);
}
返回计时器;
}
-(无效)timerTempo:(双)节奏{
if(ignoreTempoChange)return;//接收MIDI开始时设置ignoreTempoChange
_inTempo=节奏;
如果(aTimer)
{
nTicks=每秒滴答声/(节拍*24/60);//一拍的滴答声数。
nTicks=nTicks/1000;
nTicks=nTicks*1000;
调度源设置定时器(aTimer,立即调度时间,nTicks*24,0);
}
}
-(空)开始者:(双)节奏{
_inTempo=节奏;
马赫时基信息数据马赫时基信息数据;
mach_timebase_info(&mach_timebase_info_data);//在我的系统中,denum和numer始终为1???
滴答声/秒=马赫时基信息数据/秒/马赫时基信息数据/秒;
nTicks=每秒滴答声/(节拍*24/60);//一拍的滴答声数。
nTicks=nTicks/1000;
nTicks=nTicks*1000;//将nTicks四舍五入到微秒是在NI Maschine中获得坚固时钟的诀窍
clocktTimeStamp=马赫绝对时间();
调度队列q=调度获取全局队列(调度队列优先级默认为0);
aTimer=CreateDispatchTimer(nTicks*24,
0,
Q
^{
const int packetListSize=sizeof(uint32)+(25*sizeof(MIDIPacket));
MIDIPacketList*packetList=malloc(packetListSize);
MIDIPacket*packet=MIDIPacketListInit(packetList);
字节时钟=0xF8;
对于(int i=0;i<24;i++)
{
packet=MIDIPacketListAdd(packetList、packetListSize、packet、clocktTimeStamp、1和clock);
clocktTimeStamp+=nTicks;
}
MIDISend(输出端口、IAC、打包列表);
免费(包装清单);
});
timerStarted=true;
}
更新在对节奏变化的反应上取得了一些进展
//
// MidiClockGenerator.h
// MoxxxClock
//
// Created by Rob Keeris on 17/05/15.
// Copyright (c) 2015 Connector. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <CoreMIDI/CoreMIDI.h>
@interface MidiClockGenerator : NSObject
@property MIDIPortRef outPort;
@property MIDIEndpointRef destination;
@property (nonatomic, setter=setBPM:) float BPM;
@property (readonly) bool started;
@property int listSize;
- (id) initWithBPM:(float)BPM outPort:(MIDIPortRef) outPort destination:(MIDIEndpointRef) destination;
- (void) start;
- (void) stop;
@end
//
//MIDI时钟发生器
//MoxxxClock
//
//由Rob Keeris于2015年5月17日创作。
//版权所有(c)2015连接器。版权所有。
//
#进口
#进口
@接口生成器:NSObject
@属性MIDIPortRef输出端口;
@属性引用目的地;
@不动产
//
// MidiClockGenerator.h
// MoxxxClock
//
// Created by Rob Keeris on 17/05/15.
// Copyright (c) 2015 Connector. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <CoreMIDI/CoreMIDI.h>
@interface MidiClockGenerator : NSObject
@property MIDIPortRef outPort;
@property MIDIEndpointRef destination;
@property (nonatomic, setter=setBPM:) float BPM;
@property (readonly) bool started;
@property int listSize;
- (id) initWithBPM:(float)BPM outPort:(MIDIPortRef) outPort destination:(MIDIEndpointRef) destination;
- (void) start;
- (void) stop;
@end
//
// MidiClockGenerator.m
// MoxxxClock
//
// Created by Rob Keeris on 17/05/15.
// Copyright (c) 2015 Connector. All rights reserved.
//
#import "MidiClockGenerator.h"
#import <CoreMIDI/CoreMIDI.h>
@implementation MidiClockGenerator
dispatch_source_t timer;
uint64_t nTicks,bTicks,ticks_per_second;
MIDITimeStamp clockTimeStamp;
bool timerStarted;
dispatch_source_t CreateDispatchTimer(uint64_t interval,
uint64_t leeway,
dispatch_queue_t queue,
dispatch_block_t block)
{
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER,
0, 0, queue);
if (timer)
{
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, interval, leeway);
dispatch_source_set_event_handler(timer, block);
dispatch_resume(timer);
}
return timer;
}
- (void) initTemo{
nTicks = ticks_per_second / (_BPM * 24 / 60); // number of ticks between clock's.
nTicks = nTicks/100; // round the nTicks to avoid 'jitter' in the sound
nTicks = nTicks*100;
bTicks = nTicks * _listSize;
}
- (void) setBPM:(float)BPM{
_BPM = BPM;
// calculate new values for nTicks and bTicks
[self initTemo];
// Set the interval of the timer to the new calculated bTicks
if (timer)
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, bTicks, 0);
}
- (void) startTimer{
[self initTemo];
clockTimeStamp = mach_absolute_time();
// default queu is good enough on my iMac.
dispatch_queue_t q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
timer = CreateDispatchTimer(bTicks,
0,
q,
^{
// avoid to much blocks send in the future to avoid latency in tempo changes
// just skip on block when the clockTimeStamp is ahead of the mach_absolute_time()
MIDITimeStamp now = mach_absolute_time();
if (clockTimeStamp > now && (clockTimeStamp - now)/(bTicks) > 0) return;
// setup packetlist
Byte clock = 0xF8;
uint32 packetListSize = sizeof(uint32)+ (_listSize *sizeof(MIDIPacket));
MIDIPacketList *packetList= malloc((uint32)packetListSize);
MIDIPacket *packet = MIDIPacketListInit( packetList );
// Set the time stamps
for( int i = 0; i < _listSize; i++ )
{
packet = MIDIPacketListAdd( packetList, packetListSize, packet, clockTimeStamp, 1, &clock );
clockTimeStamp+= nTicks;
}
MIDISend(_outPort, _destination, packetList );
free(packetList);
});
_started = true;
}
- (id) init{
return [self initWithBPM:0 outPort:0 destination:0];
}
- (id) initWithBPM:(float)BPM outPort:(MIDIPortRef) outPort destination:(MIDIEndpointRef) destination{
self = [super init];
if (self) {
_listSize = 4; // nr of clock's send in each packetlist. Should be big enough to deal with instability of the timer
// higher values will slowdown responce to tempochanges
_outPort = outPort;
_destination = destination;
_BPM = BPM;
// find out how many machtime ticks are in one second
mach_timebase_info_data_t mach_timebase_info_data_t;
mach_timebase_info( &mach_timebase_info_data_t ); //denum and numer are always 1 on my system???
ticks_per_second = mach_timebase_info_data_t.denom * NSEC_PER_SEC / mach_timebase_info_data_t.numer;
[self start];
}
return self;
}
- (void) start{
if (_BPM > 0 && _outPort && _destination){
if (!timer) {
[self startTimer];
} else {
if (!_started) {
dispatch_resume(timer);
_started = true;
}
}
}
}
- (void) stop{
if (_started && timer){
dispatch_suspend(timer);
_started = false;
}
}
@end