Building Event-Driven LFOs in Max/MSP Gen for Jitter Frame Sync
Building frequency-controlled LFOs in Max/MSP’s event Gen requires rethinking traditional signal-rate approaches. When your LFO needs to sync with Jitter’s 30 FPS rendering instead of audio’s 44.1kHz sample rate, you can’t just use a standard phasor. This technical deep-dive reveals how to build custom accumulators, calculate per-frame phase increments, and implement phase modulation in the event domain—creating elegant, frame-synced modulation without unnecessary CPU overhead.
Building Event-Driven LFOs in Max/MSP Gen for Jitter Frame Sync
When working with Jitter for visual synthesis in Max/MSP, you often need modulation sources that sync perfectly with your render frame rate rather than audio sample rates. This creates an interesting technical challenge: how do you build a frequency-controlled LFO that updates at 30 FPS instead of 44,100 times per second?
This tutorial explores building custom oscillators in Max/MSP’s event Gen that maintain frequency-based control while operating in the event domain. You’ll learn why standard Gen oscillators don’t work for this application, how to build phase accumulators from scratch, and how to implement phase modulation synthesis techniques at frame rate rather than audio rate.
Whether you’re creating visual synthesizers, generative art projects, or simply want to understand how oscillators work at a fundamental level, this deep dive into event-based signal generation reveals elegant solutions to non-standard problems.
The Problem: Syncing Modulation with Jitter Rendering
The challenge begins with a simple requirement: create an LFO that drives jitter rendering parameters. The LFO’s update rate must sync with the jitter metro that triggers rendering, which typically runs at 30 FPS. However, you still want to control the LFO using frequency values like any normal oscillator.
In standard signal-rate Gen, this is trivial. You use a phasor object, specify your frequency, and the audio engine handles everything at 44,100 Hz. But when you’re only executing 30 times per second, this approach breaks down.
You can’t just use a signal-based Gen with snapshot to convert the output because that’s inelegant and wasteful. Why calculate 44,100 values per second when you only need 30? The solution is event Gen—a domain where patches only execute when triggered rather than continuously at audio rate.
The elegance of event Gen for this application is clear. You’re only calculating the LFO value each time you actually need it according to your jitter frame rate. This reduces CPU overhead dramatically and keeps your architecture clean.
Understanding Event Gen Versus Signal Gen
The fundamental difference between signal Gen and event Gen is execution model. Signal Gen runs continuously at your audio interface’s sample rate. Every sample, the entire patch executes and produces an output value.
Event Gen only executes when it receives a message at its inlet. Send it a bang, it calculates once. Send it a number, it uses that number in its calculations and produces one output. This makes it perfect for applications where you don’t need continuous audio-rate processing.
However, this creates a problem for frequency-based oscillators. In signal Gen, when you use a phasor and set it to 2 Hz, the phasor automatically knows it needs to increment its phase by a specific amount each sample to complete two cycles per second. It knows the sample rate is 44,100 Hz, so it calculates the appropriate increment.
In event Gen, there is no implicit sample rate. The patch doesn’t know it’s being executed 30 times per second. It just executes whenever it receives input. This means you can’t specify a frequency directly to a standard oscillator because the oscillator has no temporal context.
Building Oscillators with Accumulators
The solution is to build your own phase accumulator and calculate the per-frame increment manually. Instead of letting a phasor object handle frequency internally, you explicitly manage phase accumulation and provide the increment value yourself.
A phase accumulator is conceptually simple. It’s just a running sum that wraps between 0 and 1. Each time your patch executes, you add a small increment to the accumulated phase. When the phase reaches 1, it wraps back to 0, completing one cycle.
The key insight is calculating that increment value. Since frequency represents cycles per second, and you’re executing at a known frame rate (frames per second), you can derive the phase increment per frame with simple math.
The formula is: phase increment equals frequency divided by frame rate. If you want a 2 Hz oscillator running at 30 FPS, the increment is 2 divided by 30, which equals approximately 0.0667. Each frame, you add 0.0667 to your accumulated phase, and after 15 frames you’ve completed one cycle.
The Core Algorithm: Phase Modulation
The LFO implementation borrows its core algorithm from Curtis Roads’ “Generating Sound and Organizing Time” book. It’s essentially a phase modulation synthesizer—commonly called FM synthesis, though technically that’s a misnomer.
Phase modulation works by using one oscillator (the modulator) to offset the phase of another oscillator (the carrier). You generate two sine waves. The first is your carrier at your desired frequency. The second is your modulator, typically at some multiple of the carrier frequency.
Instead of simply adding these waveforms together, you add the modulator’s output to the carrier’s phase before calculating the carrier’s sine value. This creates complex harmonic motion and can generate sophisticated waveforms from simple sine components.
In signal-rate synthesis, this is straightforward. But in event Gen, you’re building both oscillators from accumulators. The carrier accumulator receives its calculated increment each frame. The modulator accumulator receives its own increment, typically calculated as the carrier frequency multiplied by some ratio.
The modulator’s sine output then gets scaled by a depth parameter and added to the carrier’s phase before taking the carrier’s sine. This produces phase modulation at your frame rate with frequency-based control.
Implementing Frequency Control with Coarse and Fine Tuning
Raw frequency control is useful, but musical applications often benefit from relative frequency control. The implementation includes coarse and fine frequency modulation that allows intuitive adjustment.
The coarse control uses exponential scaling. It accepts values from negative 10 to positive 10, which get used as exponents of 2. When coarse is 0, you get 2 to the power of 0, which equals 1—no change. When coarse is 1, you get 2 to the power of 1, which equals 2—double the frequency. When coarse is negative 1, you get 2 to the power of negative 1, which equals 0.5—half the frequency.
This exponential relationship means each integer change in the coarse value doubles or halves the frequency. This is musically intuitive and particularly useful for rhythmic applications where you often want frequencies that are octave-related.
The fine control provides small linear offsets to the frequency. This allows you to detune multiple LFOs slightly from each other so they drift in and out of phase, creating slowly evolving relationships. When you have two LFOs at nearly the same frequency, they create beating patterns as their phase relationship shifts.
Both coarse and fine modulation get applied before calculating the phase increment, so they affect the fundamental frequency that drives the accumulator.
Calculating Phase Increments from Frame Rate
The mathematics of phase increment calculation is crucial to making frequency control work in the event domain. You need to explicitly tell your Gen patch what the frame rate is so it can calculate appropriate increments.
The patch receives the jitter render bang two ways. Either it comes directly into the inlet, or you specify a send-receive pair name with a “clock source” message. In this implementation, “sv.jr” is the send-receive pair name, so the abstraction automatically receives bangs from that global source.
A jit.fps GUI object in the patch displays that it’s running at 30 FPS. You can verify this with the object and see the actual frame rate of your jitter rendering metro.
To calculate frames per second for use in the increment calculation, you simply take the reciprocal of the bang interval. If bangs arrive every 33.33 milliseconds (typical for 30 FPS), that’s 0.03333 seconds per frame. The reciprocal is 1 divided by 0.03333, which equals 30 frames per second.
This FPS value then divides your frequency to produce the per-frame phase increment. If you want 2 cycles per second and you’re updating 30 times per second, each update needs to advance the phase by 2/30ths of a cycle, or about 0.0667.
Managing Amplitude Range and Scaling
Raw oscillator output ranges from negative 1 to positive 1, which is standard for audio synthesis. However, when using oscillators as modulation sources for visual parameters, you often need different ranges.
The implementation includes amplitude scaling that first shifts the range from negative 1 to positive 1 into 0 to 1. This is done by adding 1 to the output (shifting range to 0 to 2) and then dividing by 2 (scaling to 0 to 1).
After this basic normalization, low and high parameters allow you to scale the output to any desired range. The low parameter sets the minimum output value, and the high parameter sets the maximum. The normalized 0 to 1 oscillator output interpolates between these bounds.
This makes the LFO incredibly flexible for driving jitter parameters that might expect values in arbitrary ranges. You’re not locked into negative 1 to positive 1 or 0 to 1—you can output 0 to 255 for color values, 0 to 10 for scale factors, or any other range your visual synthesis requires.
Phase Modulation Depth Control
The phase modulation implementation includes a depth parameter that controls how much the modulator affects the carrier. At zero depth, you get a pure sine wave at the carrier frequency. As you increase depth, the modulator begins distorting the carrier’s waveform, adding harmonic complexity.
The modulator oscillator frequency is set as a multiple of the carrier frequency. This ratio determines the harmonic relationship between carrier and modulator, which in turn affects the timbre of the resulting waveform.
Classic FM synthesis typically uses integer ratios—2:1, 3:1, 4:1—which produce harmonic spectra. Non-integer ratios produce inharmonic spectra with more bell-like or metallic qualities. For LFO applications driving visual parameters, both can be musically and visually interesting.
The depth parameter scales the modulator’s output before adding it to the carrier phase. This gives you smooth morphing from simple sine waves to complex modulated shapes. Combined with the frequency controls, this provides a surprisingly rich palette of modulation waveforms from a relatively simple algorithm.
Patch Architecture: Route Objects for API Design
Beyond the oscillator algorithm itself, the patch demonstrates excellent architecture for Max abstractions. The route object defines the patch’s API—the set of messages it responds to.
Multiple route objects handle different aspects of the interface. One routes incoming messages to appropriate parameters. Another handles the clock source setup. This creates a clean message flow where each inlet and message type has a clearly defined purpose.
Using route objects this way makes abstractions behave more like native Max objects. You send messages with clear names, and the abstraction responds appropriately. This is far superior to relying on inlet order or implicit message types.
The patch also uses patcher args (patcher arguments) piped into the route object. This allows you to set parameters directly in the object box when you instantiate the abstraction, just like native Max objects.
For example, you might instantiate the abstraction as “myLFO 2 0.5 10” where 2 is the frequency, 0.5 is a modulation parameter, and 10 is the depth. The patcher args object captures these arguments and feeds them into the route object as if they were messages, setting initial parameter values elegantly.
Type Route for Signal-Capable Abstractions
If your abstraction needs to accept both messages and signals, the type route object provides the solution. It separates signal inputs from message inputs, allowing your abstraction to process both.
This is particularly useful when building processing objects that might receive audio signals or control messages depending on context. The type route object handles the sorting automatically, sending signals to signal-rate processing chains and messages to event-rate chains.
While this specific LFO abstraction doesn’t use type route since it’s purely event-based, the architecture supports it. If you wanted to add signal-rate input modulation of any parameter, you’d simply replace the route with type route and add appropriate signal handling.
Limitations Compared to Native Objects
The one thing you don’t get with pure Max abstractions is attribute UI. Native Max objects, Gen objects, and now V8 objects support attributes that appear in the inspector with friendly UI controls.
Custom abstractions using route and patcher args don’t automatically generate this UI. Users need to send messages or use external UI objects to control parameters. This is a minor limitation but worth noting for anyone building complex tools meant for wide distribution.
For personal patches and workflow-specific tools, message-based control is perfectly adequate. You build the UI you need with standard Max objects. But if you’re creating products for others, the lack of automatic attribute UI is a consideration.
Visualizing LFO Output
The patch includes a multislider with reverse line scroll to visualize the LFO output. This provides immediate visual feedback on the waveform shape and helps when dialing in modulation parameters.
Watching the waveform scroll by, you can see how changes to frequency, modulation depth, and ratio affect the output. This is invaluable for understanding the relationship between parameters and their sonic or visual results.
For jitter applications, you might also visualize the parameter the LFO is controlling—watching a visual element respond to modulation helps verify that everything is working correctly and produces musically or visually interesting results.
Applications Beyond Jitter
While this LFO was built specifically for jitter frame-rate synchronization, the technique applies anywhere you need event-rate modulation with frequency control.
You might use it for controlling Max for Live parameters at a custom update rate independent of audio processing. You might use it for driving MIDI-controlled hardware at specific intervals. You might use it for controlling stage lighting via DMX where updates happen at fixed rates.
The principle remains the same: when you need oscillators in the event domain with frequency-based control, you build custom accumulators and calculate per-event phase increments based on your known update rate.
Learning Resources and Further Exploration
The core algorithm comes from Curtis Roads’ “Generating Sound and Organizing Time,” which is an essential text for anyone serious about computer music and synthesis. Roads breaks down synthesis algorithms into their mathematical fundamentals, making concepts like phase modulation accessible and implementable.
Understanding how to build oscillators from first principles—using accumulators, calculating phase increments, applying trigonometric functions—provides deep insight into how synthesis actually works. It demystifies the black boxes of native oscillator objects.
This knowledge transfers directly to other environments. The same principles apply in Pure Data, Csound, SuperCollider, or even lower-level languages like C++ when building audio plugins. Once you understand phase accumulation and increment calculation, you can build oscillators anywhere.
Performance Considerations
Running in event Gen rather than signal Gen provides significant CPU savings when you don’t need audio-rate processing. Calculating 30 values per second instead of 44,100 reduces computational load by over 99 percent.
This becomes crucial when running multiple LFOs or complex modulation networks. If you needed eight LFOs at audio rate just to drive visual parameters, you’d waste enormous CPU resources. Eight event-rate LFOs at 30 FPS are essentially free by comparison.
The mathematical operations involved—addition, multiplication, sine calculation, modulo wrapping—are all highly optimized in Gen. Even at audio rate these would be efficient, but at 30 FPS they’re negligible. You can run dozens of these LFOs without measurable performance impact.
Extending the Implementation
The current implementation uses sine waves for both carrier and modulator, but the accumulator approach allows any waveform. By changing what you do with the accumulated phase, you can generate sawtooth, triangle, square, or arbitrary waveshapes.
You could add wavetable lookup, reading from buffers containing custom waveforms. The phase accumulator becomes an index into the buffer, and you read the appropriate sample value each frame.
You could add multiple modulators, creating more complex FM algorithms. Two or three modulators with different frequency ratios and depths produce extremely rich waveforms from simple components.
You could add envelope generators that shape the amplitude or modulation depth over time. Combined with gate inputs, this would turn the LFO into a more sophisticated modulation generator suitable for note-based control.
Conclusion
Building event-rate oscillators with frequency control requires rethinking standard approaches but results in elegant, efficient solutions. By constructing custom phase accumulators and explicitly calculating per-frame increments, you maintain intuitive frequency-based control while operating outside the audio-rate domain.
This technique is essential for jitter-based visual synthesis where modulation sources must sync with rendering frame rates. It’s equally valuable for any application requiring precise event-rate modulation independent of audio processing.
The broader lesson is understanding oscillators at a fundamental level. When you know how to build phase accumulators, calculate increments, and implement modulation from scratch, you’re no longer limited by what objects exist. You can create exactly the modulation sources your project requires.
Whether you’re building visual synthesizers, generative art systems, or simply exploring synthesis techniques, mastering event-rate signal generation expands your creative toolkit substantially. The math is straightforward, the implementation is clean, and the results integrate seamlessly into Max/MSP workflows.
Continue Learning
Want to explore more advanced Max for Live and synthesis techniques? Check out these related tutorials:
Data Synth and Photomat: Visual to Audio Synthesis – Transform visual information into musical parameters using experimental Max for Live techniques
Resonator for Harmonic Extraction – Discover how to extract and manipulate harmonic content from audio sources in innovative ways
Advanced Push 3 Standalone Workflows – Master hardware integration techniques for sophisticated performance setups beyond traditional MIDI control




