How Jitter Mitigation Works in Nallely's Signal Processing

Nallely mitigates timing jitter by enforcing a configurable target cycle time for each virtual device thread and applying adaptive sleep calculations to maintain stable execution intervals.

The open-source Python framework dr-schlange/nallely-midi implements a virtual-device architecture where each device runs in its own thread. To combat the irregular timing variations (jitter) caused by Python's scheduler, OS load, and variable processing workloads, Nallely employs a dual-mechanism approach combining target-cycle enforcement with adaptive sleep management.

Target-Cycle Enforcement

Every VirtualDevice instance stores a target_cycle_time attribute that defines the ideal duration for one complete iteration of its main processing loop.

According to the source code in nallely/core/virtual_device.py (lines 190–223), this value defaults to 0.002 seconds (2 milliseconds) but remains configurable per device during construction via the VirtualDevice.__init__ method.

For clock-based devices, the target derives from musical tempo settings. In nallely/clocks.py (lines 106–110), the Clock._compute_target_cycle method calculates the appropriate cycle time based on the BPM parameter, translating musical beats into precise thread timing targets.

Adaptive Sleep After Each Cycle

The core jitter-reduction logic resides in the device's run loop at nallely/core/virtual_device.py (lines 504–511). After processing all pending input queues and executing the device's main generator, the framework measures the elapsed wall-clock time and sleeps only the remaining fraction necessary to reach the target cycle time.

The calculation follows this logic:

sleep_time = max(0, self.target_cycle_time - elapsed_time)

If processing exceeds the target duration, the device skips the sleep entirely and updates its next_tick_time to "catch up," preventing timing drift from accumulating across iterations.

The Capped Sleep Helper

Nallely provides a helper method VirtualDevice.sleep (lines 337–342 in nallely/core/virtual_device.py) that optionally caps requested pauses to the current target_cycle_time.

When calling sleep(t, consider_target_time=True), the method limits the pause duration to self.target_cycle_time * 1000 milliseconds. This prevents long explicit sleeps from destabilizing the device's rhythm.

However, clock devices use consider_target_time=False (as seen in nallely/clocks.py, lines 143–152) because pulse-width timing operates independently from the global target cycle, allowing precise control over musical pulse characteristics without jitter-mitigation interference.

Code Examples

Stabilizing a MIDI Clock

Create a tempo-based clock where jitter mitigation maintains steady BPM:

from nallely.clocks import Clock

# Initialize clock at 120 BPM; target cycle computed automatically

clock = Clock(tempo=120)

# The run thread enforces stable timing via adaptive sleep

clock.start()

# ... musical processing occurs here ...

clock.stop()

The Clock._compute_target_cycle method (lines 106–110) derives the target from BPM, while VirtualDevice.run (lines 504–511) maintains that rhythm through adaptive sleep calculations.

Respecting Target Cycles in Custom Devices

Use the capped sleep helper to prevent long stalls:

from nallely.core.virtual_device import VirtualDevice, VirtualParameter, on

class StabilizedDevice(VirtualDevice):
    period_cv = VirtualParameter("period", range=(0.001, 1.0), default=0.01)

    @on(period_cv, edge="rising")
    def on_period_change(self, value, ctx):
        # Request 20ms pause but cap at target_cycle_time to prevent jitter

        yield from self.sleep(20, consider_target_time=True)

When consider_target_time=True, the sleep duration cannot exceed the device's configured target_cycle_time, preserving timing regularity.

Visualizing Jitter Control

Demonstrate how the run loop smooths explicit sleeps:

import time
from nallely.core.virtual_device import VirtualDevice, VirtualParameter

class Timer(VirtualDevice):
    tick_cv = VirtualParameter("tick", range=(0, 1))

    def main(self, ctx):
        while True:
            self.tick_cv = 1
            yield 1, [self.tick_cv]
            # Explicit 5ms sleep, but adaptive loop maintains 10ms target

            yield from self.sleep(5, consider_target_time=False)
            self.tick_cv = 0
            yield 0, [self.tick_cv]

# Configure 10ms target cycle

timer = Timer(target_cycle_time=0.01)
timer.start()
time.sleep(1)
timer.stop()

Even though the generator requests a 5-millisecond pause, the surrounding run loop guarantees the total cycle remains close to the 10-millisecond target, absorbing OS-induced timing variance.

Summary

  • Target-cycle enforcement: Each VirtualDevice maintains a configurable target_cycle_time (default 2ms) set during initialization or computed from BPM for clocks.
  • Adaptive sleep calculation: The run loop measures elapsed processing time and sleeps only the remaining fraction needed to hit the target, skipping sleep entirely when catching up.
  • Capped sleep helper: The sleep method accepts a consider_target_time parameter to optionally limit pauses to the target cycle, preventing long sleeps from increasing jitter.
  • Clock independence: Clock devices bypass the target cap (consider_target_time=False) to maintain precise pulse-width control independent of the global timing target.

Frequently Asked Questions

What causes jitter in Nallely's virtual devices?

Jitter arises from Python's thread scheduler variability, operating system load fluctuations, and differences in processing time between loop iterations. Without mitigation, these factors cause irregular intervals between successive processing cycles, destabilizing MIDI timing.

How does Nallely calculate the sleep duration after each cycle?

After executing the device's main generator and processing inputs, the framework calculates sleep_time = max(0, self.target_cycle_time - elapsed_time) where elapsed_time is the measured wall-clock time for the current iteration. This adaptive calculation ensures the total cycle duration remains consistent.

Why does the Clock device use consider_target_time=False?

Clock devices control musical pulse width independently from the global timing target. By passing consider_target_time=False to the sleep method (as implemented in nallely/clocks.py lines 143–152), the clock preserves precise control over pulse characteristics without the jitter-mitigation cap interfering with musical expression.

Can I adjust the target cycle time for specific devices?

Yes. Pass target_cycle_time during device construction (default 0.002 seconds). High-priority devices benefit from shorter targets (1ms) for tighter timing, while background processors may use longer cycles (10ms) to reduce CPU load, as stored in nallely/core/virtual_device.py lines 190–223.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →