oLatency: positive or negative value?

I'm trying to wrap my head around the latency setting while using tidalcycles on machine A which is connected via ethernet to machine B that runs superdirt.

Network jitter and late messages made me dig into the latency settings. Using the Pre BundleStamp scheduling mode I can successfully get rid of the late messages when using negative oLatency settings.

Looking at the source code in Stream.hs the latency value is subtracted from the time. Using a negative value causes the event being scheduled later, right?

My vague understanding (also reading Scheduling and Server timing) tells me that when a message is sent via OSC, it gets a timestamp when it should happen at the speakers (is this correct?).

In Stream.hs, why is the latency subtracted from the time? From my intuition I would like to schedule the event on the receiving server later than the current time because it has to travel over the ethernet etc. It can't happen earlier than now, so why subtracting the latency?


There are two configuration options relating to this topic:
cProcessAhead - a global option that controls how early Tidal should calculate / generate events.
oLatency - a per target option that controls how much early the target should play the sound.

Tidal schedules events before they happen. Tidal assigns a timestamp in the future, corresponding to when the target (superdirt) should play the sound. If the timing between target and speakers needs adjustment, use oLatency. If the network is too slow, use cProcessAhead.

My blog post goes into detail: https://tidalcycles.org/blog/link_as_scheduler/

Let me know if anything needs clarification!


Thanks a lot for your help! I'm trying to figure out the timing details on my setup so that the analog stuff plays in sync with tidal.

One thing that is not yet clear to me is the relationship between the supercollider server.latency setting and the oLatency setting in the superdirtTarget. Do they add up, or do they describe different latency settings? Should they have the same value or are they set independently?

IMHO, you’ll be best off solely focusing server.latency, oLatency, etc. only on getting Tidal and SuperCollider talking reliably and efficiently.

Aligning latencies across Tidal and keyboards, desktop synths, drum machines, modular setups, etc. is best achieved using nudge. You’ll need to do some measurement to figure out which device has the highest latency and then push the others out to align with it, but that only takes a little setup in Haskell.

Declare a few lets and then you can just # ob6nudge or # nudge ob6nudge, or bake it right into your midi-related helpers so you can simply # ob6. Whatever syntax you like, really.

[Addendum: You can use negative nudge to try to pull the higher latency devices earlier in time toward the others, but only up to a limit and I found that to start interacting with the tuning of Tidal and SC latency too much. Everything was easier to sort out and tweak by only nudging things later in time, allowing the various latency settings to focus solely on Tidal and SC themselves.]

Some additional information: Nudge affects all targets. oLatency can be set per target. Nudge is used in runtime. oLatency is static in the configuration. Negative nudge corresponds to positive oLatency.

Whereby targets Zalastax means OSC targets, but note that nudge independently affects whatever you want, right down to individual events if you wish, so if you want to align a variety of hardware devices it is still the option you are looking for.

1 Like

Thank for all the valuable inputs. I played around with the timing settings and I have these findings which I would like to share with you. Some I can understand, and with some I'm confused :slight_smile:

Quick setup description. A "host" machine syncs via WIFI Ableton Link with an MPC Live. The MPC Live MIDI clock drives my modular gear. Using a latency of a oLatency = 0.044 in BootTidal.hs plays superdirt (host) with my modular gear absolutely tight.

There is s.latency = 0.1 in the startup.scd which seems to have no effect when playing sounds via superdirt.

How is s.latency in startup.scd related to oLatency in BootTidal.hs? Only the oLatency seems to matter when I go for tight sync of superdirt sound and modular gear. I can set s.latency to any value and it still plays in sync. There might be "glitches" of ~4 ms and in some situations up to 10 ms which seems reasonable to me for a windows operating system. In most of the times, it plays really tight. The cProcessAhead = 0.1 does - to my understanding - not affect the timing. It only describes how much in advance tidal should generate the events before they should be heard.

Now I use a second machine connected via WIFI (which I agree is not the best regarding latency) with the host in the same network. The "satellite" machine - which only runs tidal and not supercollider - requires a oLatency = -0.437 and a cProcessAhead = 0.5 so that the messages sent from the satellite to the host will play on the host in sync with the modular gear (still synced via Ableton Link from host via MPC).

I understand that I have to set a reasonable amount of cProcessAhead since the message needs to travel over the network etc.

I do not understand why I have to set a negative oLatency = -0.437. Btw I'm using a BPM of 60 and the pattern is hc ~ ~ ~ so there should be no 'misinterpretation in the bar or beat'.

How are the clocks between 'host' and 'satellite' machine synced? Does it indicate, that the system clocks have some difference (which would seem reasonable) or does this indicate an issue in some time sync process between 'host' and 'satellite'?

Hope this description does not feel too academic :smiley:

Edit: To illustrate how tight the Ableton Link sync works with Tidal once configured :upside_down_face: (green is envelope of modular gear and blue is the hc sound of superdirt)


I can take a stab at some of these! I don't think this is too academic, but we're definitely getting into the technical weeds so I may be getting details wrong :slight_smile:

According to this forum thread, s.latency serves the same function as cProcessAhead:

The latency of a server (e.g. Server.default.latency ) is how far in advance sclang schedules events, and thus how long the server has to make sure all the required resources are ready.

I suspect that this only matters in situations where SuperCollider is scheduling/sequencing its own patterns of events. If the messages that SuperDirt receive have timestamps, then it passes that timing information to server.makeBundle, whose documentation suggests that the bundle will be sent immediately (ignoring s.latency).

I'm not an expert on this, but I don't think they are by default, other than to an NTP server over the Internet, which isn't audio-rate accurate. Therefore, Tidal on one machine will compute OSC timestamps based on its system clock, and then SuperDirt on another machine will make sure that those events generate audio at the precise times based on its system clock. I guess there are better mechanisms (such as PTP) for synchronizing local machines, but hand-tuning an offset value that sounds good is probably the easiest solution.

Question for @Zalastax: Is it correct that clock inconsistencies between devices (or theoretically between programs on the same device) is the only use-case for oLatency, provided that OSC messages are sent with bundle timestamps?

1 Like

Thank you @mindofmatthew for the additional details.

I now get really tight sync between network machines given that I synchronize the system clocks with high accuracy (see Microsoft Windows Documentation and stackoverflow)

My strategy was this on the host machine:

  1. Set a reasonable s.latency. I've set it to s.latency = 0.2 which works well.
  2. Configure the LinkClock with a l.latency = 0.225 so that the audio metronome click of the MPC and a click from supercollider are tight. The value seems reasonable to me since it is close to the recommended server latency.
  3. In BootTidal.hs I use Pre BundleStamp and a oLatency = 0.045 which gives tight timing of a hc ~ ~ ~ pattern with the previous metronome clicks.

On the satellite machine after synchronizing the system clocks with high accuracy (on both machines), I can use a much more reasonable value of o.Latency = 0.038 so that the hc ~ ~ ~ pattern from the satellite machine plays in sync with the metronome clicks, i.e., with the hc pattern of the host machine.

  • I do not know yet how stable this sync is over time. I assume that slight re-tuning may be required to get really tight (~1 ms) timing.
  • With system clock sync, I have reasonable values in all settings :wink:

There was a discussion on Github or this forum about sending the beat information to SuperDirt and connect SuperDirt to Link too, rather than sending timestamps. The advantage would be that we don't need to accurately sync system clocks. It should not be a huge amount of work for someone that understands the current code somewhat.

I just want to mention that after some struggle with late messages in SuperCollider I got rid of them by reducing oLatency from 0.3 to 0.05. Current values:

-- BootTidal.hs
oLatency = 0.05

// Supercollider startup.scd
s.latency = 0.6;