vivid-wavetable is an audio-first modular wavetable synthesis package for Vivid. Active graphs are written against the fixed-cadence core and use _au core control operators explicitly where clocking, envelopes, and modulation are needed.
- PolyVoiceAllocator — converts MIDI and control inputs into polyphonic lane arrays (frequencies, gates, velocities, lane_ids)
- WavetableOsc — polyphonic wavetable oscillator with family/member source selection, phase/drift motion controls, warp modes, unison, and conditioned oscillator interaction
- AnalogOsc — polyphonic virtual analog oscillator with PolyBLEP anti-aliasing (sine, saw, square, triangle, pulse) and conditioned oscillator interaction
- SubOsc — polyphonic sub oscillator (sine, triangle, saw, square, noise)
- NoiseLayer — polyphonic per-note noise/air source for breath, attack detail, and texture layers
- VoiceDrive — lane-preserving soft drive for per-voice body, glue, and velocity-sensitive harmonic density
- VoiceMixer — sums N-channel per-voice audio to stereo with panning, velocity, envelope control, and optional output glue
src/— operator source filesfactory_presets/— per-operator factory presetsgraphs/core/wavetable_modular_demo.json— core smoke graph (modular chain)graphs/presets/— curated showcase librarytests/— package testsarchive/— legacy WavetableSynth monolith (frozen, not built)
From vivid-core:
./build/vivid link ../vivid-wavetable
./build/vivid rebuild vivid-wavetableIf you are new to the package, start here.
This is the beginner path for building a usable polysynth graph with the current lane architecture. It is meant to teach graph construction and musical intent, not debugging. If you want to verify that an operator is behaving correctly or isolate a bug by listening to tiny proof graphs, use the validation guide instead:
Create these nodes:
ClockAuasclockChordProgressionAuaschordsPolyVoiceAllocatorasvoices
Connect:
clock/beat_phase -> chords/beat_phase
chords/notes -> voices/notes_in
chords/velocities -> voices/velocities_in
chords/gates -> voices/gates_in
Recommended starting params:
clock/bpm = 96voices/max_voices = 6
What this does musically:
ClockAukeeps timeChordProgressionAuproduces note eventsPolyVoiceAllocatorturns those notes into one lane per note
Create:
WavetableOscasosc
Connect:
voices/frequencies -> osc/frequencies
voices/gates -> osc/gates
voices/velocities -> osc/velocities
voices/lane_ids -> osc/lane_ids
Recommended starting params:
osc/amplitude = 0.25osc/wavetable_family = AnalogWarmosc/wavetable_member = Coreosc/position = 0.35osc/unison_voices = 2osc/unison_spread = 12
What this does musically:
- this is the raw tone source
- before reduction and envelopes, it will sound rougher than a finished preset, and that is okay
Create:
EnvelopeAuasamp_envVoiceMixerasmixeraudio_outasout
Connect:
voices/gates -> amp_env/gate
osc/output -> mixer/input
amp_env/value -> mixer/amp_env_audio
voices/velocities -> mixer/velocities
mixer/output -> out/input
Recommended starting params:
amp_env/attack = 0.01amp_env/decay = 0.25amp_env/sustain = 0.70amp_env/release = 0.40mixer/stereo_spread = 0.70
What this does musically:
EnvelopeAushapes each note independentlyVoiceMixeris where the separate note lanes become one stereo output- this is the first point where the patch should sound like a real playable synth instead of a raw lane test
Create:
FilterasfilterEnvelopeAuasfilt_env
Rewire:
osc/output -> filter/input
filter/output -> mixer/input
voices/frequencies -> filter/frequencies
voices/gates -> filt_env/gate
filt_env/value -> filter/cutoff_mod
Recommended starting params:
filter/mode = LowPassfilter/cutoff = 2200filter/resonance = 0.18filt_env/attack = 0.02filt_env/decay = 0.50filt_env/sustain = 0.20filt_env/release = 0.35
What this does musically:
- adds note-shaped brightness and movement
- makes the synth feel played rather than statically bright
Once the basic synth is working, layer in one extra character block at a time:
SubOscfor low supportNoiseLayerfor air, breath, and transient detailVoiceDrivefor body and per-voice saturation before reduction
Typical connections:
voices/frequencies,gates,velocities,lane_ids -> SubOsc/...
voices/frequencies,gates,velocities,lane_ids -> NoiseLayer/...
osc/output -> VoiceDrive/input
voices/velocities -> VoiceDrive/velocities
VoiceDrive/output -> mixer/input
Good first-use settings:
SubOsc/level = 0.20NoiseLayer/level = 0.06NoiseLayer/tone = 0.68VoiceDrive/drive = 0.18VoiceDrive/tone = 0.52
After the basic graph feels clear, try the two most important extension paths:
- wavetable motion:
LfoAu/value -> osc/position_mod_audio- or
EnvelopeAu/value -> osc/position_mod_audio
- oscillator interaction:
- feed another oscillator into
osc/mod_input - start with
interaction_mode = PM - then raise
interaction_depthslowly
- feed another oscillator into
Recommended interaction starting point:
interaction_mode = PMinteraction_depth = 0.18interaction_input_gain = 1.0interaction_tracking = 1.0
After building the basic synth once by hand, these retained graphs are good next examples:
graphs/presets/single_osc_motion_reference.jsonfor clear wavetable motiongraphs/presets/airy_keys.jsonfor Pass 3 layeringgraphs/presets/fm_glass_keys.jsonfor Pass 4 interaction
The old synth-building doc is now just a pointer:
The package now ships a deliberately curated preset library instead of carrying every experiment from the expansion passes forward.
- Showcase overview:
docs/showcase-library.md - Retained motion reference:
graphs/presets/single_osc_motion_reference.json - Clear Pass 3 reference:
graphs/presets/airy_keys.json - Clear Pass 4 reference:
graphs/presets/fm_glass_keys.json
The retained library is organized around eight listening families:
- pads and beds
- keys and brass
- plucks and bells
- leads
- basses
- textures and drones
- arp and sequence patches
- cinematic hybrids
WavetableOsc now organizes built-in tables as family + member instead of one flat coarse selector.
- Families:
AnalogWarm,BrightDigital,VocalFormant,Metallic,HarmonicSpectral,TextureMotion - Shared members:
Core,Soft,Rich,Hollow,Sweep,Glass,Edge,Air
The shared member labels are intentionally approximate tonal roles so presets can move between families without changing how the control surface reads.
Pass 3's character-layering surface is built around three lightweight roles:
NoiseLayerfor per-note air, breath, and transient detailVoiceDrivefor body and per-voice harmonic glue before reductionVoiceMixer.gluefor subtle post-sum cohesion on dense layered sounds
Pass 4 redesigns oscillator-to-oscillator interaction around one shared carrier-side model in WavetableOsc and AnalogOsc:
interaction_mode=Off,FM,PM,RM,AMinteraction_depthfor the musical amountinteraction_input_gainfor how hard the incoming modulator drives the carrierinteraction_trackingfor how much the interaction follows carrier pitch
PM is the preferred starting point for stable glass and metallic keys/leads. Use FM when you want stronger growl or more obviously pitch-coupled interaction. RM and AM are now depth-aware and intended to be dialed, not used as all-or-nothing tricks.
The package CI workflow:
- Clones and builds vivid-core (
test_demo_graphs+ core operators). - Builds package operators and all package tests, including
test_audio_correctness. - Runs package
ctestagainst the active modular surface. - Runs graph smoke tests against
graphs/core/plus focused hero/reference batches fromgraphs/presets/after copying the package dylibs into the vivid-core build. - Leaves
archive/out of active smoke coverage.
MIT (see LICENSE).
