[sdiy] MIDI Clock sync advice
Spiros Makris
spirosmakris92 at gmail.com
Sun Mar 17 09:01:29 CET 2024
I will try to explain what I did to model the analog PLL behaviour in
software, maybe it helps as a starting point. (I'm not formally familiar
with a DPLL and what I am doing is probably likely isn't the recommended
approach).
i assume you have a method of receiving external clocks. This might be some
MIDI input (USB, UART), a digital pin or an internal timer. Upon receiving
the clock you can run a function (either as a real interrupt, or a
callback, like it's common in the Arduino MIDI library).
Additionally, you have some internal clock generator. This can be a
hardware timer, a NCO, a millis() timer, anything will do (some will be
horrible, but they work, in principle.
The first thing we need, as you already found out, is a way to deduce the
relative phase/frequency of the two signals.
The PFD (phase-frequency detector) is a very versatile one used in
analogue, and since it's designed using strictly digital components (as
opposed to, say, a mixer acting as a phase detector) we are a step closer
to our platform.
This is an example of a PFD output:
[image: image.png]
There are two outputs, UP and DN. The concept is that UP is activated with
a Ref clock and DN with an internal clock. If both outputs find themselves
ON at the same time, both are reset. If CLKref is leading CLKvco (as in the
picture), you end up with a pulse train on the UP output whose duty cycle
indicates "how much" you need to increase your vco frequency for it to
catch up with Ref. DN also has a series of short pulses; those are more or
less part of how it is implemented in hardware, rather than core to its
functionality, so let's ignore them.
The most common way to convert this into a VCO control signal is to
integrate it. In the analog domain, it's usually called a "charge pump",
and it looks like this:
[image: image.png]
Eventually, the sign of Vout tells you which signal is leading. The
magnitude indicates by how much for how long, and cannot be directly
translated to any physical value, but turns out that if you feed it to the
VCO the system will tend to lock the two clocks in frequency and phase.
I tried to apply this approach in code and here is what I came up with:
We are going to keep a count variable that will play the role of "Vout".
This variable will decrease while MIDI Clk is leading, and increase while
Internal CLK is leading. Additionally, we define a variable that will hold
the state of this lead-lag relationship, and conveniently, it will have 3
states: -1 (decreasing), 0 (not changing), 1 (increasing).
This is the state machine we are implementing:
[image: image.png]
The transitions noted are implemented in the respective clock's
callback/ISR.
Independently of this, we run a simple timed interrupt some magnitudes
above the frequencies we are working with (constant rate). The selected
frequency will have an impact on the value scales you will work with, and
eventually the bandwidth and gain of the system, so in a real
implementation it will be important for stability. The code of this
interrupt is:
count +=phase_state
set_timer_period(Tref + K* count)
Tref is a constant number we selected as the system's initial period. I set
that to 120 BPM, as a common middle point for music. The closer this is to
the input frequency the faster the system will lock to it.
K is a float that works as a gain. This will scale the corrections produced
by the PFD for the BPM we selected. A low value will need more time to lock
(because your count variable needs more cycles to increase enough for the
required change). A high value will lock faster, but too high will
eventually lead to oscillation (this K is similar to the Kvco found in an
analogue PLL, or Ki found in a PID controller).
It doesn't sync to beats, but I think the clocks should be synced down to
the 24PPQ level. While it does lock to the input frequency (you will need
to fiddle with K), abrupt changes don't work very well. Since we are in
software we easily implement a frequency measurement mechanism to detect
these changes and produce a "feed forward" term that will nudge our PLL to
the new value faster. Even if we take a few cycles to settle on the new
value, applying an abrupt increase in the same order of magnitude as the
input should at least keep the beat timing between the two consistent (the
transition time is what messes that up, after all).
Hopefully, this helps! I'll try to tidy up my code and share it later.
On Sun, 17 Mar 2024 at 02:32, Tom Wiltshire <tom at electricdruid.net> wrote:
> I've had a crack at the software PLL idea and I can't get it to work.
>
> What I don't understand is how one clock "earlier" or "later" than the
> other (and this difference can only ever be 180 degrees maximum, and even
> which of those is going on can be hard to tell) can tell you which clock is
> "faster" or "slower". Perhaps it might work when the clocks are very close,
> but we're talking about something that has to lock in over a 1:10 range. If
> the internal clock is at 30bpm, and the external clock is at 200bpm, what
> does the phase difference *really* mean? If you look at five different
> cycles (of which clock?) you get five different answers. So I still don't
> get it, and that's reflected in code that doesn't work. Presumably there's
> something I'm still missing since PLLs are old tech that clearly does work.
> Am I expecting it to do something it can't do? Is 1:10 too wide a lock
> range for a PLL to work? Does such a range require a very slow filter,
> perhaps? What's going on?
>
> The alternative approach of "measure the time, lock it in" obviously does
> work. That's simple. But it does leave questions hanging about what you do
> about phase. Is a hard reset every X clocks really the best option? We
> could do something cleverer...but then there's also the question of jitter
> in the incoming clock. You don't really want to hard-sync to clock that
> might not be that solid. It would be better to stick to a longer term
> average of the incoming pulses, but that requires something else going on.
>
> Anyway, thanks for all the discussion thus far. There's been a lot of
> interesting stuff come up.
>
> Tom
>
> ==================
> Electric Druid
> Synth & Stompbox DIY
> ==================
>
>
>
> > On 16 Mar 2024, at 22:10, brianw <brianw at audiobanshee.com> wrote:
> >
> > I suspect that hard-sync / reset could cause some hiccups, but maybe
> only at the start ... and when there are tempo changes.
> >
> > I still have not entirely figured out how a PLL works, especially not in
> software (even with timers and interrupts to help) as opposed to a pure
> hardware PLL. However, the hardware PLL automatically syncs like a DJ who
> is trying to beat match: they speed up or slow down one record until the
> other is both the same tempo and same phase of the beats. For a software
> PLL, you'd need some way to know which clock is ahead of the other. With
> timer interrupt assist, that probably means reading the timer when MIDI
> Clock arrives, and some method of determining where the timer is in its
> period at that moment. Spiros' implementation might work with the addition
> of some way to "look at" the internal clock to see where it's at in its
> period when the MIDI Clock is received.
> >
> > Measuring the period and matching that will get the tempo right, but
> with no sync. Comparing the state of the two clocks (external versus
> internal divided) will phase lock the two, but that's more challenging in
> software than in hardware. Sure, hard-sync is not difficult, but the
> smoother phase lock is what I'm saying would be more challenging.
> >
> > Perhaps a higher internal clock rate - more than double speed - would
> reduce the fluctuation of the PFD values. That's just a guess, though. How
> much resolution do you have, Spiros? Are we talking about a fluctuation of
> one LSB? ... or does the measurement fluctuate more than that?
> >
> > Your USB-MIDI without interrupts is interesting, because I assume that
> the Device must be using interrupts to receive the USB data. Then again,
> maybe the USB peripheral uses DMA to put the data directly in memory, and
> then the client code just polls the registers to see when something shows
> up. I recall that the USB Device libraries that I use sometimes allow a
> choice between interrupt-based and polled. In any case, USB-MIDI is going
> to be somewhat different from classic asynchronous serial MIDI.
> >
> > As Roman mentioned, you can implement the serial MIDI interrupt as a
> hybrid: Handle MIDI System Real Time events inside the interrupt, but queue
> the rest. This is effectively filtering out System Real Time messages from
> the stream that ends up in the queue, and perhaps your MIDI parser can be
> slightly simpler with the knowledge that there will never be any System
> Real Time events in the queue.
> >
> > Regarding the new generation of programmers, I'm both happy and sad.
> Having more people exciting about programming is great. However, when new
> stuff gets prototyped by amateurs; hyped into a startup; and result in
> commercial products that are poorly engineered but "good enough" ... I just
> find it tedious to sift through the cruft. Lots of consumers get excited by
> the newness, and when things work poorly they cry for open source (even
> though few can program any better) or dismiss the problem as the fault of
> "MIDI" - in contrast, it's amazing what can be done when there is no waste.
> >
> > I hope some of this makes sense,
> >
> > Brian
> >
> >
> > On Mar 16, 2024, at 7:38 AM, Amos wrote:
> >> PS) I just re-read the end of your message and saw that you were
> thinking of exactly the mechanism I suggested (hard-syncing the output
> every quarter note). I don't know whether it seems wrong or not, but I can
> confirm that it does the job. :)
> >>
> >> On Sat, Mar 16, 2024 at 9:34 AM Amos <controlvoltage at gmail.com> wrote:
> >>> One small thought I’d add is that once you’re happy with your
> clock-frequency detection, one way to handle phase is to every so often do
> a hard-reset of your output timer phase on receipt of an incoming clock, so
> that you output a clock at the next possible instant. I suppose you could
> be fancy and keep track of the time delta by which your output leads/lags
> incoming clocks, and reset phase when this drifts beyond an acceptable
> measure, but in practice I think I’ve just done this every quarter note or
> something like that (it’s been a while).
> >>>
> >>> In any case, adding some mechanism like this can ensure that your
> clock output stays pretty well in sync with the beat.
> >>>
> >>> On Saturday 16 March 2024, Spiros Makris via Synth-diy <
> synth-diy at synth-diy.org> wrote:
> >>>> On Fri, 15 Mar 2024 at 17:47, Roman Sowa via Synth-diy <
> synth-diy at synth-diy.org> wrote:
> >>>>> If I may just add - maybe it's just my impression, but I think that
> new
> >>>>> generations of programmers educated today have absolutely no idea how
> >>>>> microprocessor works.
> >>>>
> >>>> That feels true, but we should keep in mind that "programmer" has
> been a continuously expanding group of people, and you can now be a
> programmer starting from a wild variety of disciplines, most of which don't
> start from the bottom, as is customary in electrical engineering and
> (sometimes) computer science degrees. Most programs I know of have
> computers and operating systems as a standard class, but microcontrollers
> and embedded systems as an elective. That is, you can't become an embedded
> programmer "by accident", unlike python, java and others, which any STEM
> major will have some knowledge of.
> >>>>
> >>>> I gave it a quick try on a teensy 4. I used USB-MIDI and fed a clock
> from Ableton, without using interrupts. I made all the wrong choices, I
> know.
> >>>> So my strategy was to create something that resembles a phase
> frequency detector: I created an internal clock that will run at double the
> speed of the incoming clock, and implemented a /2 divider. I am listening
> for clocks from either USB or the internal clock, and switch between
> decreasing and increasing a count variable based on the lead-lag
> relationship of the incoming messages. The result resembles the
> functionality of a PFD coupled with a charge pump/integrator filter, and it
> does lock to the incoming tempo. It will follow abrupt tempo changes
> reasonably fast, but then sync between beats is lost. Furthermore, I can
> see the PFD values fluctuating, which does happen in such a configuration,
> but I would expect less of a ripple under a good lock. I suspect Ableton
> clocks are fairly jittery through USB, so I'll give it another go using a
> more stable clock and figure out if the ripple is system instability or
> Ableton acting up.
> >>>> Another approach I intend to try is simply measuring the incoming
> frequency using a reference timer and then assigning the value (multiplied
> by a factor) to a second timer to act as a clock. I know it will more or
> less work, but doesn't really have any syncing mechanism; I expect the
> resulting beats will be out of sync.
> >>>> I am contemplating adding a syncing mechanism that keeps track of the
> number of incoming beats and introducing a secondary feedback mechanism
> following the quarter notes. It feels like a wrong approach though.
> >>>>
> >>>>
> >>>> On Fri, 15 Mar 2024 at 17:47, Roman Sowa via Synth-diy <
> synth-diy at synth-diy.org> wrote:
> >>>>> W dniu 2024-03-15 o 13:57, René Schmitz pisze:
> >>>>>>
> >>>>>>
> >>>>>> The speed of processors roughly keeps up with programmers ability
> to
> >>>>>> waste it.
> >>>>>
> >>>>> Yes, that's my feeling exactly
> >>>>>
> >>>>>>
> >>>>>> My gripe is that, for beginner programmer, it does not necessarily
> teach
> >>>>>> good practices, and even for a seasoned one it proliferates some
> bad
> >>>>>> habits. (How often is everything in loop()...)
> >>>>>>
> >>>>>> It's like giveing you a nice sugar coated piece of cake, so you
> might
> >>>>>> never be motivated to learn how to bake a better one.
> >>>>>
> >>>>> hell yeah!
> >>>>>
> >>>>> If I may just add - maybe it's just my impression, but I think that
> new
> >>>>> generations of programmers educated today have absolutely no idea
> how
> >>>>> microprocessor works.
> >>>>>
> >>>>>
> >>>>>> Back to MIDI: generally, when using the serial interrupt you have
> two
> >>>>>> choices:
> >>>>>>
> >>>>>> Either make the ISR short, and for example just throw the data into
> a
> >>>>>> buffer.
> >>>>>>
> >>>>>> Or do all the processing (which could take some time) there. Risk
> is
> >>>>>> that you might block other ISRs, such as timers, which is of course
> bad.
> >>>>>
> >>>>> If I need a buffer, I usually process only MIDI clock inside ISR and
> put
> >>>>> all other messages into the buffer. Less than 20 cycles worst case.
> But
> >>>>> for example in my MIDI note decoder there's no buffer at all. The
> work
> >>>>> is done before next byte comes in, so there's no chance of
> cluttering or
> >>>>> missing a message.
> >>>>>
> >>>>> Roman
> >>>>
> >
> > ________________________________________________________
> > This is the Synth-diy mailing list
> > Submit email to: Synth-diy at synth-diy.org
> > View archive at: https://synth-diy.org/pipermail/synth-diy/
> > Check your settings at: https://synth-diy.org/mailman/listinfo/synth-diy
> > Selling or trading? Use marketplace at synth-diy.org
>
>
> ________________________________________________________
> This is the Synth-diy mailing list
> Submit email to: Synth-diy at synth-diy.org
> View archive at: https://synth-diy.org/pipermail/synth-diy/
> Check your settings at: https://synth-diy.org/mailman/listinfo/synth-diy
> Selling or trading? Use marketplace at synth-diy.org
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://synth-diy.org/pipermail/synth-diy/attachments/20240317/9da3c1ac/attachment.htm>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: image.png
Type: image/png
Size: 91317 bytes
Desc: not available
URL: <http://synth-diy.org/pipermail/synth-diy/attachments/20240317/9da3c1ac/attachment.png>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: image.png
Type: image/png
Size: 7637 bytes
Desc: not available
URL: <http://synth-diy.org/pipermail/synth-diy/attachments/20240317/9da3c1ac/attachment-0001.png>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: image.png
Type: image/png
Size: 10463 bytes
Desc: not available
URL: <http://synth-diy.org/pipermail/synth-diy/attachments/20240317/9da3c1ac/attachment-0002.png>
More information about the Synth-diy
mailing list