私は小さなプログラムに取り組んでおり、ジッタリングしている着信 MIDI クロックを安定したビートに「変換」しています。ジッタリングする MIDI クロックがひどいトレモロ サウンドを生成します。
アイデアは、着信 MIDI クロックを「聞き取り」、テンポを決定した後、安定した MIDI クロックを仮想 IAC デバイスに送信して、DAW (NI マシン) を同じ IAC デバイスに同期できるようにすることです。着信 MIDI は KORG Electribe からのものなので、MIDI ケーブルにこだわっています。Komplete Audio 6 を使用して MIDI クロックを受信しています。
最初の部分 (テンポを聞いて決定する) は既にカバーされていますが、そのテンポの安定したクロックを生成する必要があります。
優先度の高いスレッドを使用して midi クロックを送信しようとしました。以下のテスト ルーチンでは、テンポが 119.8 から 120.2 の間で変動します。
このルーチンで何か間違ったことをしましたか、それとも別の戦略を使用する必要がありますか? どんな助けでも大歓迎です。
よろしく、 ロブ
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) testTimer{
IAC = MIDIGetDestination(0); // 0 is the MAC IAC device on my system
MIDIPacket pkt;
MIDIPacketList l;
pkt.timeStamp = 0;
pkt.length = 1;
pkt.data[0] = 0xF8;
l.numPackets = 1;
l.packet[0] = pkt;
dispatch_queue_t q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
aTimer = CreateDispatchTimer(20833 * NSEC_PER_SEC/1000000, // 20.8333 ms will give me tempo 120
0,
q,
^{
MIDISend(outPort, IAC, &l ); // outport was already created outside this code
});
アップデート
うまくいく戦略を考え出した。以下のコードは、私のシステムで完璧な結果をもたらします。バンドとのギグですでに使用しましたが、問題なく動作しました。
私にとっての解決策は次のとおりです。
- 単一のクロックを送信する代わりに、24 個のクロックでパケットリストを送信する
- 最初のクロックで現在のマッチタイムを使用してタイムスタンプのみを設定し、計算されたティック数でタイムスタンプをインクリメントし続けます。(現在のマッチタイムが packetlist の最初の各パケットに設定されている場合、結果は安定しませんでした!)
- 計算されたティックをマイクロ秒に丸めます! これには驚きました.. 精度が高いほど良い結果が得られる.. しかし、ナノ秒の精度を使用すると、DAW (NI Maschine) の画面でテンポは安定していましたが、それでも「ジッター」サウンドがありました。これが 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;
}
更新
テンポの変更への対応を改善しました。
- MIDITimeStamp の固定値が mach_absolute_time() よりもはるかに進んでいる場合、パケット リストの送信を停止します。
- 24 クロックではなく 8 クロックのみで小さなパケット リストを送信する
私のシステムでは、テンポの変更はスムーズに送信され、遅延は最小限に抑えられますが、テンポを複数回変更した後、送信側の MIDI デバイスと生成された MIDI クロックをリッスンする DAW のビートに小さなオフセットが発生する可能性があります。
ライブ パフォーマンスでは、これは送信側の MIDI デバイスを使用する「ドラマー」が、サウンドを再び同期させるために、送信側のデバイスで停止と開始を実行する必要があることを意味します。私のバンドにとって、これは問題ではありません。急なストップ&スタートは効果抜群!
最適化されたコードの下。使いやすいようにクラスにラップしました。改善点があれば回答お願いします。
//
// 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