Description
The current LfoBlock implementation provides basic functionality but lacks several standard features found in typical synthesizer LFOs. While the codebase already has 6 waveforms implemented (Sine, Square, Sawtooth, Triangle, Pulse, Noise), only Sine is currently exposed through the GraphBuilder API.
Current capabilities:
- Frequency parameter (0.01 Hz to ~43 Hz at 44.1kHz/512 buffer)
- Depth parameter (amplitude scaling)
- Both parameters are modulatable
- SIMD-optimized processing
- Control-rate output (one value per buffer)
Missing standard LFO features:
- Waveform selection - Waveforms exist internally but aren't exposed to users
- Offset/polarity control - No way to shift output range (e.g., unipolar 0-1 vs bipolar -1 to 1)
- Fade-in/delay - No gradual onset for modulation
- Phase sync/retrigger - No mechanism to reset phase on note-on or sync to external events
- Tempo sync - No integration with host tempo/BPM
Open questions
- Should waveform be a runtime-changeable parameter, or set once at construction?
- Should we add a simple unipolar / bipolar toggle, a continuous offset parameter, or both?
- In a plugin context, can the LFO access note-on events from the DSP layer? If not, retrigger functionality may need to be deferred or limited to standalone / terminal synth use cases. Alternative approaches include:
- Expose a
reset_phase() method that external code can call
- Accept a trigger input from another block
- Defer to a future issue once MIDI routing to modulators is established
- How should tempo / BPM be communicated to the LFO?
- Via a parameter that the host / plugin sets each buffer
- Via a dedicated tempo input from the graph context
- Tempo sync note divisions (1/4, 1/8, 1/16, dotted, triplet, etc.)
- Should users be able to set an initial phase offset (0-360 degrees)? Useful for layering multiple LFOs
- Should fade-in time be time-based (seconds) or tempo-synced (bars/beats)? Should it restart on retrigger?
Acceptance criteria
Waveform Selection
- Users can select from all available waveforms (Sine, Square, Sawtooth, Triangle, Pulse, Noise) when creating an LFO
- The selected waveform produces the expected output shape
Offset/Polarity Control
- Users can configure the LFO to output in bipolar mode (-1 to +1, centered at 0)
- Users can configure the LFO to output in unipolar mode (0 to +1)
- The depth parameter correctly scales the output in both modes
Fade-In/Delay
- Users can configure a delay time before the LFO begins modulating
- Users can configure a fade-in time for gradual modulation onset
- The LFO output smoothly transitions from zero to full depth during fade-in
Tempo Sync
- Users can sync LFO rate to tempo (BPM) instead of specifying frequency in Hz
- Standard note divisions are available (1/1, 1/2, 1/4, 1/8, 1/16, 1/32)
- Dotted and triplet variants are available for each division
- LFO rate updates correctly when tempo changes
Phase Reset
- Users can programmatically reset the LFO phase
- Phase resets happen cleanly without audible artifacts
- Free-running mode (current behavior) remains available
Documentation
- LFO documentation reflects all new parameters and modes
- Examples demonstrate common LFO configurations (e.g., tempo-synced tremolo, unipolar vibrato with fade-in)
Tasks
TBD
Description
The current
LfoBlockimplementation provides basic functionality but lacks several standard features found in typical synthesizer LFOs. While the codebase already has 6 waveforms implemented (Sine, Square, Sawtooth, Triangle, Pulse, Noise), only Sine is currently exposed through the GraphBuilder API.Current capabilities:
Missing standard LFO features:
Open questions
reset_phase()method that external code can callAcceptance criteria
Waveform Selection
Offset/Polarity Control
Fade-In/Delay
Tempo Sync
Phase Reset
Documentation
Tasks
TBD