Dedicated Midi-Clock Sync - Module idea - Programmer needed

Hi Rackheadz,

I spend the last few days with about 100 tests to get a stable midi-clock IN and OUT of VCV Rack to sync recording with Ardour and Renoise and found 2 main issues

  1. if receiving a Midiclock in VCV’s Midi-CV or @modll’s MIDIpoly16 in 24 PPQN mode the BPM jitters because some dsp::PulseGenerations are dropped (somewhere in the void??)
  2. If sending a clock (x24 ofc) with CV-Midi, it is pretty stable, BUUUT the Start and Stop Messages (0xFA and 0xFC) lack or get dropped sometimes. Both behaviours also have slight differences depending on the VCV sample rate used.

There are already a couple of topics regarding Midi-Clock sync in this forum and on github and there is already a workaround by sending an analogue Clock Pulse (or Pulse.wav) to VCV, or using Beatstep Pro as master => @Omri_Cohen made a nice video on this, @VCVRackIdeas has another video using the VCV Bridge to get a stable syncing in 0.6.

VCV already uses rtmidi and the sync24 (24 PPQN) option, so the core-functionallity is already build in.

I solved a partial on the Receiving a clock with MIDI-CV by chaining 2 Clocked and on uses a fixed BPM, but then, like on sending Midi-clock, I still have the problem, that the startpoint of recordings differ on multiple recording aproaches like somehow the Midistream is queued or buffered somewhere.

After reading the midi-core implementation of VCV regarding the modules MIDI-CV and CV-MIDI and comparing it with moDll’s MIDIpoly 16’s code and the dedicated midiclock.cpp test examples of rtmidi on github I came to some conclusions

  1. My Programming skills in C++ (or javascript or lua) are not good enough to programm a module on my own :woozy_face::disappointed_relieved:
  2. A dedicated Midi-Clock sync module, that directly listenes to the Midistream only for 0XF8 to 0XFC without queueing anything might better the stability
  3. This module could act as independent clock master and slave
  4. Renoise is known for it’s stable Midi-clock, that btw, is sends out permanently if set a master (like a VCO)
  5. and again => => my programming skills are not good enough :sob::sob::face_with_symbols_over_mouth:

Because there is a lot of development going on and a lot of different approaches, I wanted to gather some feedback on this module idea first and if this should be a good idea yes …

we-want-you

Looking for module-developers, that will make this real.

To answer some questions in advance:

Q: why don’t you simply use an external analogue clock like ERM or a Beatstep or …?

A: because I’m pretty broke most of the time and some other HW synth have priority (and befor that my Kids ofc . one is in puberty, so parents understand :smiley: :smiley: )

Q: Why don’t you use VCV Bridge or send a Pulse.wav?

A: I tried but the setup is a bit complicated, plus, if we solve this as a module it might have some benefits for the midi-core in general

Q: why do you use Ardour and Renoise … xxxxx is much better in everything?

A: because :smiley: :smiley: :smiley: (just a joke question cause - on other discussionboards that would be the first thing getting asked turning all the thread to a big fight over a software - luckily our VCV community is different)

1 Like

Might give it a go as I’m currently already working on MIDI-related modules… I can run some tests and see if we can have an improvement on this or not at least…

One question : how do you currently send receive MIDI from and out VCV ? Loopback ? Dedicated MIDI interface ?

2 Likes

To get a really accurate clock signal you should use an audio stream instead of MIDI. Using a pulse.wav is one way to go, another one would be transporting MIDI over an audio stream as the Expert Sleepers USAMO does it or like the ESX-8GT expanders.

2 Likes

@23volts … merci :hugs:

o.k. now some details:

  • I tested it on Linux Mint 19.1 (low-latency kernel) with kxstudio’s Cadence ( so JackMidi) and Alsa Midi using Ardour and Renoise as source and receiver (Master and Slave)
  • I also used my Hardware Midibox Sequencer V4 (from Ucapps.de) that normally sends a very stable clock that already synced 6 HW-Synths, a DIY Modular mith Midi2CV interface and Renoise + Ardour (Alsa only)
  • I tested it on Windows 10 with loopMidi sending and receiving from Ardour, Cakewalk and Midi-OX
  • I tested it with multiple recordings and multiple resolutions of VCV sample rate (44.1 kHz to 192 kHz) with different clock-divisions

I could get a workaround for the the clock jitters, but then I had drops or latency on the Start and Stop (0xFA and 0xFC) messages

I made a little test, just listening to 0xF8 messages and DEBUG() the results. At 44.1Khz, there’s an average of 200-300 samples jitter between each clock pulse…

Tried with Ableton sending clock with loopback, and with my Eventide Space sending MIDI Clock with USB, same results…

I’d agree with @stoermelder on the pulse.wav > MIDI clock. That’s what I use and there is no jitter at all by using this method.

I guess a module which apply some smoothing to the incoming clock could give improved results, but bear in mind it will never be as accurate as an audio clock.

2 Likes

@stoermelder … that is right, this would give a really acurate clock signal, but then there might still be a latency in the Start and Stop /Continue behaviour, right?

Plus, on my setup it would mean, sending the clock from VCV via Audio-Interface with minimal latency to the USAMO and via a second Midi-interface back into Ardour, right?

Could you explain this workflow? I used to send triggers via midi or CV via Jack and it give a perfect accurate it is something similar?

1 Like

That is absolutely right and when sending a Clock (x24) via CV-Midi, there is a minimal jitter, but Ardour does not listen to BPM-Info anyway :rofl:. So if I set the BPM fix to the same BPM as VCV all is o.k.ish for me. Even when recording within VCV and importing a multi-wav into Ardour with same BPM all is perfect. But there is still a queue or latency when sending or receiving 0xFA (start) and this might be (but I am not a pro here) because of the placement of the rtmidi Callbacks ??? . rtmidi has also the function called ignoreTypes() so this maybe could give a difference, when just listening to those 0xF messages.

IF (and this is a big IF) I read the API/Sources of rtmidi right, incoming messages are written into a queue befor processing.

It is unavoidable to use a queue (MIDI messages have to be serialized and you can’t send two or more messages the same time) and have some latency (MIDI has a much lower „transmission rate“ than audio, 31250 bits per second, without overhead you get 3125 bytes per second and most MIDI messages are 3 bytes long, so about 1000 messages per second).

2 Likes

If you want you can contact me about that privately. I have been working too on a collection of MIDI modules but nothing ready for release yet.

3 Likes

Like I said, this thread is for gathering ideas, plus my coding skills are … well lets say, when I learned programming there was a company called SUN that did JAVA and PHP was called Private HomePage tool and was not object-oriented at all :joy::joy:

yes, therefor the idea of a separate module, cause as far I unterstand it 0xF-Mesages are only 1 byte long, so this module could ignore all messages bigger than one byte, maybe (big maybe) this gives enhancement:

 if (message->size() != 1)
    return;

and I found this:

// RtMidiIn constructor
    midiin = new RtMidiIn();
....... 
// Set our callback function.  This should be done immediately after
// opening the port to avoid having incoming messages written to the
// queue instead of sent to the callback function.
    midiin->setCallback( &mycallback, &clock_count );

in https://github.com/thestk/rtmidi/blob/master/tests/midiclock.cpp

and then maybe directly push the prefilterd messages it into the function that calls dsp::PulseGenerator

 unsigned int msg = message->at(0);
  if (msg == 0xFA)
    dsp::PulseGenerator.trigger(1e-3);
  if (msg == 0xFB)
    dsp::PulseGenerator.trigger(1e-3);
  if (msg == 0xFC)
    dsp::PulseGenerator.trigger(1e-3);
  if (msg == 0xF8) {
#something to include 24 PPQN and maybe others
 dsp::PulseGenerator.trigger(1e-3);
....

But that is as far as my skills take me :D :D
1 Like

This might work. You just need to call PluseGenerator::process in Module::process and output 10V if it returns true.

1 Like

And on the sending part of this module (so to say, as master), no calc’s on IF this message is a system-message, but directly sending to the rtmidi output

if(bool starttrigger)
  message.push_back( 0xFA );
if(bool stopTrigger)
  message.pushback( 0xFC );
if(bool clkTrigger)
  message.pushback( 0xF8 );

message.clear();  
midiout->sendMessage( &message );

or something like this

btw. here i noticed, that Renoise f.e. starts sending the 0xF8 directly and all the time, even if no Track is running or recording, so that other clock receivers can already adjust to the BPM. This, paired with a dedicated Midi-Channel in the setup (Midi-system messages only on Midi-Channel 1) might be a nice idea too.

If we then can set the BPM in sync24 (24 PPQN) as deltatime in this module it could

if (mode == clockMaster)
  message.pushback( 0xF8 ) 
 midiout->sendMessage(&message) 'til the end of the universe and back in deltatime;

Ofc the other midi system-messages (Start /stop / continue) have higher priority over clock and can override this message IF 2 of the trigggers occur at the same time because the compensation of this is the up to the other software.

Haven’t read this whole thread, but my guess of what you’re experiencing is that the Rack MIDI driver doesn’t attempt to synchronize MIDI timestamps with audio. The midi::Message struct has no timestamp field. Therefore, MIDI messages are consumed as soon as they enter the queue of MIDI-CC etc, regardless of when they happened relative to the audio buffer.

Please note that I don’t support “virtual” audio/MIDI devices, only real hardware. However, this is an issue with hardware that sends MIDI timeclocks.

1 Like

Thanks @Vortico for your feedback.

and yes, this module (or code) is only intended regarding the Midi-System Message issues wich real hardware (or other software) have when sending and receiveing Midi-System Messages.

To explain my intension a bit: I came up with this idea cause I tend to use VCV more and more as my main DAW with my other synths and DIY Modular (yes, even my Midibox Seq V4 gets dusty :smiley: :smiley: ). The correct timing on start (0xFA) could open a new way of composing for me, where VCV is the Master and I can use Ardour’s (or Cakewalk’s or …) recording features that also offer overdub or layering of tracks. I can then also use features like “location markers” or “punch recording” I can then sequence and record my HW-Synth in seperate sessions, … to be continued

1 Like

slightly OT, but I wouldn’t use Beatstep as a benchmark for stable Midi clock. I sold mine partly because it drifted so much (i was lucky also to be able to get a Cirklon without having to wait for 2 yrs…). I’d vouch for the ERM, which is decent and useful for all sorts of things (swing / offset).

ps. ymmv, Arturia drivers may have got better, but for me it was poor.

There is a 50% swing defaulted on a Beatstep there is no way to change it to 0. Not sure if the Pro has that ability.

haha that is kinda hilarious! I had the Beatstep pro for a while but it was too long ago for me to remember the features exactly. I think it had swing… but defo remember the timing jitter was considerable.

I never noticed that swing on my beatstep.

Hi all,

still wrapping my noob mind over this and have no clue if @stoermelder and @23volts are also wrapping their minds over this.

Am I correct, that, on VCV startup the midi structures are initiated and all incoming Midi-messages are written to the InputQueue :: Input (in Midi.hpp) with a size of

int queueMaxSize = 8192; ??

All midi Modules I see always call

midi::InputQueue midiInput;

to process the midimessages?

And also in midi.hpp

struct Message {
	uint8_t size = 3;

assumes that all midimessages are 3 byte long? Wich works perfect on Midimessages, except SytemMessages.

So, there are 2 option:

  1. create a new virtual Midiinterface that includes rtmidi directly, wich is, correctly, not supported by @Vortico or
  2. rework the existing core-files with an inputswitch, that,
if (message->size == 1) => Don't write to queue
else size = 3;

and add a new struct that can be called only for the special case of the SystemMessages?

Or is this over the top? Can I already access the midi::message directly and filter, if messages are size == 1 and process thse and skip all the rest?

Would the second option be worth as a request on github?

1 Like