Midiclock class


#1

Hi,

it took me a while to get my Midiclock class working perfectly but finally here
is my “ready to use” class to inject Midiclock sync messages into a given
MidiBuffer. The most of the time was used to optimize timing to let the class
deal with position jumps e.g “loops” a perfect way. It was also quite difficult
(for me) to find the best way of injecting events to have different Midiclock
slaves behaving the same way. I’ve tested with Cubase 6.5, Ableton Live,
Reaper and Native Instruments Reaktor. All of them stay perfectly in sync
using my class.

The class is very easy to use, just make it a member of your Audioprocessor class
and feed its generateMidiclock method with some values which you can
extract from LastTimeInfo struct.
To let the class deal with loops I had to hack the juce_VST_Wrapper.cpp as well as
juce_AudioPlayHead.h as shown below. Hopefully Jules can check this in shortly.

To minimize jitter I use a high priority thread to pass the MidiBuffer containing the events
to MidiOutput::sendBlockOfMessages(). There were several discussions about that here
in the forum. MidiOutput::sendBlockOfMessages() allocates memory on the heap
that introduces a global lock and we should never block the audio callback!!!
Using a lockless FIFO might be a better approach…

There are some parameters to tweak the class. One important is “setOffset()”. This
parameter allows you to compensate the latency of the MIdiOuput device…

Feel free to use the class at your own risk! It would be nice to hearing some feedback and
hopefully the class will make its way into the juce framework even if Jules will completely re-write it 

This is the juce_VST_Wrapper hack:

    bool getCurrentPosition (AudioPlayHead::CurrentPositionInfo& info)
    {
        const VstTimeInfo* const ti = getTimeInfo (kVstPpqPosValid | kVstTempoValid | kVstBarsValid | kVstCyclePosValid
                                                   | kVstTimeSigValid | kVstSmpteValid | kVstClockValid);

        if (ti == nullptr || ti->sampleRate <= 0)
            return false;

        info.bpm = (ti->flags & kVstTempoValid) != 0 ? ti->tempo : 0.0;

        if ((ti->flags & kVstTimeSigValid) != 0)
        {
            info.timeSigNumerator = ti->timeSigNumerator;
            info.timeSigDenominator = ti->timeSigDenominator;
        }
        else
        {
            info.timeSigNumerator = 4;
            info.timeSigDenominator = 4;
        }

        info.timeInSeconds = ti->samplePos / ti->sampleRate;
        info.ppqPosition = (ti->flags & kVstPpqPosValid) != 0 ? ti->ppqPos : 0.0;
        info.ppqPositionOfLastBarStart = (ti->flags & kVstBarsValid) != 0 ? ti->barStartPos : 0.0;

        if ((ti->flags & kVstSmpteValid) != 0)
        {
            AudioPlayHead::FrameRateType rate = AudioPlayHead::fpsUnknown;
            double fps = 1.0;

            switch (ti->smpteFrameRate)
            {
                case kVstSmpte24fps:        rate = AudioPlayHead::fps24;       fps = 24.0;  break;
                case kVstSmpte25fps:        rate = AudioPlayHead::fps25;       fps = 25.0;  break;
                case kVstSmpte2997fps:      rate = AudioPlayHead::fps2997;     fps = 29.97; break;
                case kVstSmpte30fps:        rate = AudioPlayHead::fps30;       fps = 30.0;  break;
                case kVstSmpte2997dfps:     rate = AudioPlayHead::fps2997drop; fps = 29.97; break;
                case kVstSmpte30dfps:       rate = AudioPlayHead::fps30drop;   fps = 30.0;  break;

                case kVstSmpteFilm16mm:
                case kVstSmpteFilm35mm:     fps = 24.0; break;

                case kVstSmpte239fps:       fps = 23.976; break;
                case kVstSmpte249fps:       fps = 24.976; break;
                case kVstSmpte599fps:       fps = 59.94; break;
                case kVstSmpte60fps:        fps = 60; break;

                default:                    jassertfalse; // unknown frame-rate..
            }

            info.frameRate = rate;
            info.editOriginTime = ti->smpteOffset / (80.0 * fps);
        }
        else
        {
            info.frameRate = AudioPlayHead::fpsUnknown;
            info.editOriginTime = 0;
        }

        info.isRecording = (ti->flags & kVstTransportRecording) != 0;
        info.isPlaying   = (ti->flags & kVstTransportPlaying) != 0 || info.isRecording;
		
		if ((ti->flags & kVstCyclePosValid) != 0)
        {
			info.cycleStartPos = ti->cycleStartPos;
			info.cycleEndPos = ti->cycleEndPos;       
		}
		
		info.isTransportCycleActive = (ti->flags & kVstTransportCycleActive) != 0

        return true;
    }

and in AudioPlayhead.h…

    struct JUCE_API  CurrentPositionInfo
    {
        /** The tempo in BPM */
        double bpm;

        /** Time signature numerator, e.g. the 3 of a 3/4 time sig */
        int timeSigNumerator;
        /** Time signature denominator, e.g. the 4 of a 3/4 time sig */
        int timeSigDenominator;

        /** The current play position, in seconds from the start of the edit. */
        double timeInSeconds;

        /** For timecode, the position of the start of the edit, in seconds from 00:00:00:00. */
        double editOriginTime;

        /** The current play position in pulses-per-quarter-note.

            This is the number of quarter notes since the edit start.
        */
        double ppqPosition;

        /** The position of the start of the last bar, in pulses-per-quarter-note.

            This is the number of quarter notes from the start of the edit to the
            start of the current bar.

            Note - this value may be unavailable on some hosts, e.g. Pro-Tools. If
            it's not available, the value will be 0.
        */
        double ppqPositionOfLastBarStart;

        /** The video frame rate, if applicable. */
        FrameRateType frameRate;

        /** True if the transport is currently playing. */
        bool isPlaying;

        /** True if the transport is currently recording.

            (When isRecording is true, then isPlaying will also be true).
        */
        bool isRecording;
		
		/** The current cycle start position in pulses-per-quarter-note. */
		double cycleStartPos;
		
		/** The current cycle end position in pulses-per-quarter-note. */
		double cycleEndPos;
		
		/** True if the transport cycle is currently active. */
		bool isTransportCycleActive;

        //==============================================================================
        bool operator== (const CurrentPositionInfo& other) const noexcept;
        bool operator!= (const CurrentPositionInfo& other) const noexcept;

        void resetToDefault();
    };

Hope this class is usefull to somebody.

Best,
Joerg


#2

Cool stuff. No problem adding some loop position values like that, but I think I’ll call them “loop” rather than “cycle” positions.


#3

This looks very useful. I have to eventually add support for sending proper midi clock messages (I’m assuming that’s what your class does, correct me if wrong).

But why is it necessary to do this from a separate high priority thread? Can’t it be done in the audioDeviceIOCallback? I thought that tying the clock to the stream of output samples would always maintain perfect accuracy. I don’t know a whole lot about midi output (having dealt mostly with the input side).


#4

No, the class itself doesn’t need a high priority thread but most of the host don’t support
sending Sysex or realtime Midi messages through its audio callback so you will need to
send the MidiBuffer to a physical Midi device instead, using MidiOutput::sendBlockOfMessages().
But this methode creates objects on the heap and will block the audio callback if you pass the MidiBuffer
directly from the audio callback!

The class does exactly what you need, injecting Midiclock and start/stop messages.

Joerg


#5

[quote=“Joerg Koehler”]But this methode creates objects on the heap and will block the audio callback if you pass the MidiBuffer
directly from the audio callback! [/quote]

UGH…okay, I took a closer look at the MIDI I/O in Juce. It looks…synchronous. I was expecting that MIDI output messages would go out with the audioDeviceIOCallback. Isn’t there a better way this could be done?

Yes!! That is exactly what I need! But this business of synchronous operation and the blocking nature of sendBlockOfMessages() has to be resolved.

FYI I’m implementing the host side not the plugin.


#6

Not to my knowledge. It is really a big limitation in the vst interface (and I guess AU as well) and it seems that nobody of the
serious DAW developer pays any attention to it.

As said, using a lockless FIFO might be better but even then you’ll need a high priority thread to fire the events
on time using MidiOutput::sendMessageNow().

I haven’t looked at the host implementation in juce but I think you will have much more room to play as this Midi limitation
will perhaps not exist inside the host itself and you could simply inject the clock events after the audio callback. Just a guess…


#7

Yeah, I have tried that. Just creating a new high-priority thread (using juce classes) wasn’t good enough for controlling the SMPTE display of my digital Soundcraft desk at least. It skips and jumps. It works well with Reaper though, as it buffers the incoming Midi clock messages I guess.

You might get it to work using some other high-precission timer, but I know nothing about those, and I guess it is rather OS specific. If anybody knows something about this, let me know and I’ll try it and report back.


#8

Just took a look at your class and realized that you are dealing with MIDI beat clock, whereas I was talking about MIDI time code generation. Not the same thing, but maybe similar. Sorry for the confusion!

Thanks for sharing by the way!


#9

Yeah Vinn, I totally see your point, being on the host-side as well.
What we would need is an implementation of this :
http://www.tweakheadz.com/sync_mmc_mtc_smpte.htm

based on callbacks. However, this code would be a good starting point. We can easily get rid of the high priority thread, being host developpers :slight_smile:


#10

Good call. That’s a really nice page. So it turns out, after reading that page, that what I care about is the “MIDI Clock”, for synchronizing LFO effects to the current tempo / song BPM. Although it seems that both are important because some devices want one, some want the other (and presumably some are buggy regardless of what you provide).


#11

Yeah totally ! Plus, I guess some VSTs implemented buggy implementations on purpose, because they knew the hosts would be buggy. It also depends if you want to synchonize with external hardware. Things can get a bit more complicated than it seems :slight_smile:


#12

Definitely not a problem of using a high priority thread. I also have a Midi Timecode class running, based on the same
class using the same thread and that works perfectly. My challenge was to get Cubase slaved to Ableton and
my Timecode class does it perfectly. Very tight timing, I could even put the number of frames to analyze down to zero in Cubase
and haven’t had any dropped frames.

You should better double check the accuracy of your inject method to minimize jitter from the beginning.


#13

[quote=“Joerg Koehler”][quote]
Just creating a new high-priority thread (using juce classes) wasn’t good enough for controlling the SMPTE display of my digital Soundcraft desk at least. It skips and jumps.
[/quote]

Definitely not a problem of using a high priority thread. I also have a Midi Timecode class running, based on the same
class using the same thread and that works perfectly. My challenge was to get Cubase slaved to Ableton and
my Timecode class does it perfectly. Very tight timing, I could even put the number of frames to analyze down to zero in Cubase
and haven’t had any dropped frames.

You should better double check the accuracy of your inject method to minimize jitter from the beginning.[/quote]

I don’t know a lot about these things, so you are probably right. But at the moment, my thread creates a new quarter frame message, then sends it using sendMessageNow, and finally calls wait(), to sleep for ~8 ms until it is time to wake up and send a new message. I don’t see how this can be much more accurate, as it is still up to the OS scheduler to decide when to wake up that thread. Or am I missing something here?


#14

Well, I’ve spend ages to find the most accurate timing and learned that quarterframe messages must arrive quite exactly as the slaves also sync to the time when these messages arrive. Also, check whether the time information in the fullframe and quarter messages are correct. Midi OX on windows was my best friend here…


#15

Yeah, it seems like the timing of each quarterframe is important. And thats the difficult part of it, because I haven’t figured out how to reliably send midi messages with better-than millisecond timing. I think I will try to sync using analog SMPTE signals instead. Then you are always synchronized with the audio buffers.


#16

Thank you for checking the loop stuff in, Jules!


#17

Hey Joerg,

Very interesting class!
Let me get it right though: you are using this -in a plugin- for sync-ing two hosts together via a midi-port (virtual or not, I suppose)?
I might want to use parts of your class in something we’re working on! :slight_smile:

  • bram

#18

Yes, it’s what it can do although Midiclock is rather useful for syncing Synthesizer’s LFO and stuff
because it tends to drift over the time.

Feel free to use it if it helps…

Joerg


#19

Hi Joerg,

Thanks for your class! I’ve installed it in my Juce application (a standalone one, not a plug), and it does the trick.
But i’ve have some sensible jitter sometimes. I’ve tried to use a high priority thread to pass the MidiBuffer produced
by your generateMidiClock to the sendBlockOfMessages, but peharps there’s something i missed about the timing.
I first use the getMillisecondCounterHiRes() to set the initial time then i use ppQ to calculate the time passed
to sendBlockOfMessages.
Thanks for your sharing!


#20

Is your standalone a host?
I haven’t yet tested the class in a standalone/host app but I would start searching the problem in your clock generator or whatever
you are using to generate the ppq value. The class seems to be very low in producing jitter in a plugin but is of course relying on
a jitterless clock source (audio callback). I’ve also changed the way of passing the blocks to Midi output. I am now using a lockless FIFO to put
the generated sync Midi messages to the Midi output which works great.

May you can share your code or explain your way of providing high res ppq?

Joerg