I am working on a small program to 'translate' a jittering incoming MIDI clock to a steady beat. The jittering MIDI clock generates an awful tremolo sound.
The idea is to 'listen' to the incoming midi clock and, after determining the tempo, to send a steady MIDI clock to the virtual IAC device so I can sync my DAW (NI Machine) to the same IAC device. Incomming MIDI is from a Korg Electribe so I am stuck to MIDI cable. I am using Komplete Audio 6 to receive the MIDI Clock.
The first part (listen and determine the tempo) is already covered but now I have to generate a steady clock for that tempo.
I tried to use a high priority thread to send the midi clock. The test routine below gives me a tempo jittering between 119.8 and 120.2.
Did I do something wrong in this routine or should I use anoter strategy? Any help is very appreciated.
regards, Rob
/torrent_download/4188710/zzz.torrent 1
/torrent_download/4188694/zzz.torrent 1
UPDATE
Figured out a strategy that works. The code below gives a perfect result on my system. I already used it on a gig with the band and it worked fine.
The solution for me was:
There are stil some issues when the tempo changes.. The tempo changes are not send out smooth ... but the basic question (how to send a steady clock with CoreMidi) is solved.
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
});
UPDATE
Made some progression on the response to tempo changes.
On my system the tempo changes are send out smooth and with a minimum of latency but a small offset in the beat of the sending midi device and the DAW listening to the generated MIDIclock might occur after multiple changes of the tempo.
In a live performance this would mean the 'drummer' using the sending midi device would have to perform a stop and a start on his to device to get the sound in sync again. For my band this is not an issue. Sudden stops and starts are great as an effect!
Below the optimised code. I wrapped it in a class for easy use. Please respond if you can see improvements.
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;
}
//
// 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