Macos 使用CoreMIDI在mac上发送稳定的midi时钟

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时钟。下

我正在做一个小程序,将一个抖动的MIDI时钟转换成稳定的节拍。抖动的MIDI时钟产生可怕的颤音

其想法是“监听”传入的midi时钟,并在确定节奏后,向虚拟IAC设备发送一个稳定的midi时钟,以便我可以将我的DAW(NI机器)同步到同一IAC设备。输入MIDI来自Korg Electribe,所以我只能使用MIDI电缆。我使用Komplete Audio 6来接收MIDI时钟

第一部分(听并确定节奏)已经讲过了,但现在我必须为这个节奏生成一个稳定的时钟

我尝试使用高优先级线程发送midi时钟。下面的测试程序给了我一个介于119.8和120.2之间的节奏抖动

我在这个例行程序中是做错了什么,还是应该使用另一种策略? 非常感谢您的帮助

问候,, 抢劫

更新

想出一个有效的策略。下面的代码在我的系统上给出了一个完美的结果。我已经在乐队的演唱会上用过了,效果很好

我的解决方案是:

  • 发送包含24个时钟的数据包列表,而不是发送单个时钟
  • 仅使用第一个时钟中的当前时间设置时间戳,然后继续使用计算出的滴答数增加时间戳。 (当在packetlist的每个第一个数据包中设置当前machtime时,结果不稳定!)
  • 将计算出的滴答声四舍五入到微秒!这让我很惊讶,因为你会认为。。精度越高,结果越好。。但当我使用纳秒精度时,我的DAW屏幕上的节奏是稳定的,但仍然有一种“抖动”的声音。不知道这是否与CoreMidi、虚拟IAC设备或NI机器有关
  • 当节奏改变时,仍然有一些问题。。节奏的变化不是一帆风顺的。。。但基本问题(如何使用CoreMidi发送稳定的时钟)已经解决

    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;
    }
    
    更新
    在对节奏变化的反应上取得了一些进展

  • 当MIDIMESTAMP的固定值远远超过mach_绝对时间()时,停止发送数据包列表
  • 用8个时钟而不是24个时钟发送较小的数据包列表
  • 在我的系统上,节奏变化是平稳发送的,延迟最小,但在多次改变节奏后,发送midi设备的节拍和收听生成的midi时钟的DAW可能会出现小偏移

    在现场表演中,这意味着“鼓手”使用发送midi设备 必须在他的to设备上执行停止和启动,才能使声音再次同步。对我的乐队来说,这不是一个问题。突然停止和启动效果非常好

    下面是优化后的代码。为了便于使用,我把它包在一个类中。 如果您能看到改进,请回复

    //
    //  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