This document is for advanced users who want to create their own composite events in an event_definitions.yml file. It details the core building blocks available in the FunscriptEditor.
Every operation is applied to a specific axis (e.g., volume, pulse_frequency) at a specified start_time_ms for a duration_ms.
Operations can target multiple axes simultaneously by using comma-separated axis names. This provides explicit control over which funscript files are affected:
Examples:
- Single axis:
axis: volume- affects only volume.funscript - Multiple axes:
axis: volume,volume-prostate- affects both volume.funscript and volume-prostate.funscript - Multiple axes:
axis: alpha,alpha-prostate,beta,beta-prostate- affects all four files
This means if you want to apply a modulation to both normal and prostate volume files, use axis: volume,volume-prostate.
All parameter values are automatically normalized from their axis-specific units to the internal 0.0-1.0 funscript range. The normalization is configured in the normalization section of your event definitions file:
normalization:
pulse_frequency:
max: 200.0 # Values in Hz (e.g., 100 Hz → 0.5 normalized)
unit: "Hz"
pulse_width:
max: 100.0 # Values in % (e.g., 50% → 0.5 normalized)
unit: "%"
frequency:
max: 1200.0 # Values in Hz (e.g., 600 Hz → 0.5 normalized)
unit: "Hz"
volume:
max: 1.0 # Already normalized (0.0-1.0)
unit: "normalized"Normalization Rules:
- If
maxis 1.0, values are assumed to already be normalized (no conversion) - If
max> 1.0 and your value is ≤ 1.0, it's assumed to be pre-normalized (no conversion) - Otherwise, the value is divided by
maxto normalize it
Examples:
pulse_frequency: 100→ normalized to100 / 200.0 = 0.5pulse_width: 50→ normalized to50 / 100.0 = 0.5volume: 0.3→ already normalized, stays0.3frequency: 600→ normalized to600 / 1200.0 = 0.5
These operations modify existing values within a funscript. They are the most common and versatile building blocks.
This is the most powerful operation for creating dynamic effects. It adds or overwrites a segment of a funscript with a generated waveform.
Use Cases:
- Creating a "buzz" or "hum" on the
volumeaxis (likeedgeorspike). - Creating a wide, slow oscillation on the
volumeaxis (liketranquil). - Making the
pulse_frequencyorpulse_widthwaver rhythmically (liketantalize).
Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
axis |
string |
(Required) | The funscript axis to target (e.g., volume, pulse_frequency, alpha, beta). Can specify multiple axes using comma-separated names (e.g., volume,volume-prostate). |
duration_ms |
int |
(Required) | The duration of the effect in milliseconds. |
waveform |
string |
(Required) | The shape of the wave. Supported waveforms: • sin - Smooth sinusoidal oscillation • square - Square wave (use with duty_cycle) • triangle - Linear ramp up and down • sawtooth - Linear ramp up with instant drop |
frequency |
float |
(Required) | The frequency of the wave in Hz. Avoid multiples of 10 Hz (10, 20, 30...) to prevent sampling aliasing issues. Use values like 9, 11, 15, 21, 23, 65 Hz instead. |
amplitude |
float |
(Required) | The swing amplitude of the wave in axis-specific units (will be normalized). The wave oscillates ±amplitude around the center point. For example, amplitude: 0.35 on volume creates oscillations from -0.35 to +0.35 relative to the center. |
max_level_offset |
float |
0.0 |
Offset for the maximum level of the waveform in axis-specific units (will be normalized). • In additive mode: relative to the original values. Example: max_level_offset: 0.0 means peaks reach the original level, -0.1 means peaks are 0.1 below original. • In overwrite mode: sets the absolute maximum level. • The center point is automatically calculated as: max_level_offset - amplitude. This makes it easy to keep peaks at a desired level without manual offset calculations. |
phase |
float |
0.0 |
The starting phase of the wave, in degrees (0-360). Use this to offset the waveform's starting position. |
duty_cycle |
float |
0.5 |
For square waveform only: the percentage of time at maximum value (0.01-0.99). Default 0.5 is 50% duty cycle (equal high/low time). Higher values = more time at max, lower values = more time at min. Ignored for other waveforms. |
mode |
string |
additive |
How to apply the effect: • additive: final = original + (max_level_offset - amplitude) + amplitude*waveform(...) • overwrite: final = (max_level_offset - amplitude) + amplitude*waveform(...) |
ramp_in_ms |
int |
0 |
Duration in milliseconds for a linear fade-in of the wave's amplitude envelope. |
ramp_out_ms |
int |
0 |
Duration in milliseconds for a linear fade-out of the wave's amplitude envelope. |
Mathematical Formulas:
First, the center offset is calculated:
offset = max_level_offset - amplitude
Then applied:
- Additive mode:
final = original_value + normalized_offset + normalized_amplitude * waveform(frequency, time, phase) - Overwrite mode:
final = normalized_offset + normalized_amplitude * waveform(frequency, time, phase)
Where waveform() generates values in the range [-1, +1] based on the selected waveform type:
- sin:
sin(2π * frequency * time + phase)- smooth sinusoidal oscillation - square:
+1for duty_cycle% of period,-1for remainder - sharp on/off transitions - triangle: linear ramp from -1 to +1 (first half), then +1 to -1 (second half)
- sawtooth: linear ramp from -1 to +1, then instant reset
Important Notes:
- All waveforms are bipolar (range from -1 to +1), creating true oscillations above and below the center point.
- Final values are clipped to 0.0-1.0 range after all operations to prevent out-of-bounds values.
- Ramp envelopes are applied to the entire generated wave, creating smooth fade-in/fade-out effects.
- Sampling aliasing: If your modulation frequency matches or is a multiple of the funscript sample rate (~10 Hz), you may get no oscillation because all samples land at the same phase. Use frequencies like 9, 11, 15, 21, 23, 65 Hz instead of 10, 20, 30, 60 Hz.
- Square wave duty cycle: Use duty_cycle < 0.5 for short pulses (more time at min), > 0.5 for wide pulses (more time at max).
This operation is for simple, non-oscillating changes, like setting a value, applying a boost, or creating a simple ramp.
Use Cases:
- Applying a
volume_boostfor the duration of an effect. - Setting the
pulse_frequencyto a new constant value. - Creating a long, slow fade-in or fade-out effect on any axis.
- Ramping between two values over time.
Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
axis |
string |
(Required) | The funscript axis to target. Can specify multiple axes using comma-separated names (e.g., volume,volume-prostate). |
duration_ms |
int |
(Required) | The duration of the effect in milliseconds. |
start_value |
float |
(Required) | The value of the effect at its beginning, in axis-specific units (will be normalized). |
end_value |
float |
start_value |
The value of the effect at its end, in axis-specific units (will be normalized). If different from start_value, creates a linear ramp between the two values. |
mode |
string |
additive |
How to apply the effect: • additive: Adds the linear change to existing values. • overwrite: Replaces existing values with the linear change. |
ramp_in_ms |
int |
0 |
Duration in milliseconds for a fade-in envelope applied to the entire effect. |
ramp_out_ms |
int |
0 |
Duration in milliseconds for a fade-out envelope applied to the entire effect. |
Mathematical Formula:
- Linear interpolation: Values are interpolated linearly from
normalized_start_valuetonormalized_end_valueover the duration. - Additive mode:
final = original_value + interpolated_value * envelope - Overwrite mode:
final = interpolated_value * envelope
Important Notes:
- If
start_value == end_value, this creates a constant value for the duration (no ramp). - Ramp envelopes are applied multiplicatively to the linear values, allowing smooth transitions in and out.
- Final values are clipped to 0.0-1.0 range after all operations.
Events are composed of multiple steps, each representing one operation. Steps can have different start_offset values to sequence operations in time.
event_name:
default_params:
# Define default parameter values with descriptive names
duration_ms: 10000
volume_boost: 0.05
buzz_freq: 15
steps:
# Step 1: Starts at event time (offset 0)
- operation: apply_linear_change
axis: volume
start_offset: 0
params:
start_value: $volume_boost
end_value: $volume_boost
duration_ms: $duration_ms
mode: additive
# Step 2: Starts 200ms after event time
- operation: apply_modulation
axis: volume
start_offset: 200
params:
waveform: sin
frequency: $buzz_freq
amplitude: 0.1
duration_ms: $duration_ms
mode: additiveUse $parameter_name to reference values from default_params. This allows:
- Reusability: Users can override defaults when placing events
- Clarity: Descriptive names instead of magic numbers
- Calculations: Simple math like
$duration_ms - $start_offset
Example user event file:
events:
- time: 5000
name: edge
params:
duration_ms: 12000 # Override default
buzz_freq: 11 # Override defaultThis example shows how to create a multi-layered effect with synchronized operations on different axes:
intense_edge:
default_params:
duration_ms: 15000
pulse_rate: 120 # Hz - will be normalized by max 200.0
volume_boost: 0.05 # Already normalized (0.0-1.0)
buzz_freq: 11 # Hz - avoid aliasing
buzz_amplitude: 0.08 # Normalized
alpha_freq: 9 # Hz - avoid aliasing
alpha_amp: 0.35 # Normalized
ramp_ms: 500
steps:
# 1. Set high pulse frequency
- operation: apply_linear_change
axis: pulse_frequency
start_offset: 0
params:
start_value: $pulse_rate # 120 Hz → normalized to 0.6
end_value: $pulse_rate
duration_ms: $duration_ms
ramp_in_ms: $ramp_ms
ramp_out_ms: $ramp_ms
mode: overwrite
# 2. Add volume boost
- operation: apply_linear_change
axis: volume
start_offset: 0
params:
start_value: $volume_boost # Already normalized
end_value: $volume_boost
duration_ms: $duration_ms
ramp_in_ms: $ramp_ms
ramp_out_ms: $ramp_ms
mode: additive
# 3. Add high-frequency buzz on volume
- operation: apply_modulation
axis: volume
start_offset: 0
params:
waveform: sin
frequency: $buzz_freq # 11 Hz (avoid 10 Hz aliasing)
amplitude: $buzz_amplitude # ±0.08 oscillation, e.g., 0.08
max_level_offset: 0.08 # Peaks reach 0.08 above original (same as amplitude for centered oscillation)
duration_ms: $duration_ms
ramp_in_ms: $ramp_ms
ramp_out_ms: $ramp_ms
mode: additive
# 4. Modulate both alpha axes
- operation: apply_modulation
axis: alpha,alpha-prostate
start_offset: 0
params:
waveform: sin
frequency: $alpha_freq # 9 Hz (avoid 10 Hz aliasing)
amplitude: $alpha_amp # ±0.35 oscillation
max_level_offset: 0.90 # Peaks reach 0.90 (center at 0.55)
duration_ms: $duration_ms
ramp_in_ms: $ramp_ms
ramp_out_ms: $ramp_ms
mode: additiveWhat this does:
- Sets pulse frequency to 120 Hz (high intensity) with smooth ramp in/out
- Adds 5% volume boost throughout the effect
- Overlays an 11 Hz buzzing sensation on top
- Modulates both alpha and alpha-prostate axes with a 9 Hz wave centered at 0.55
Avoid sampling aliasing by not using frequencies that are multiples of 10 Hz:
- ❌ Bad: 10, 20, 30, 60, 100 Hz
- ✅ Good: 9, 11, 15, 17, 21, 23, 65, 120 Hz
- Use
ramp_in_msandramp_out_msto create smooth transitions - Short effects (3-5s): ramp_ms = 200-250
- Medium effects (5-10s): ramp_ms = 500
- Long effects (>10s): ramp_ms = 1000-2000
- Additive mode: Best for effects that layer on top of existing patterns
- Overwrite mode: Best for completely replacing values in a time segment
- Group related parameters together in
default_params - Use descriptive names that explain the purpose
- Provide sensible defaults that create a good effect without user customization
- Use comma-separated axis names to target multiple axes explicitly (e.g.,
axis: volume,volume-prostate) - Synchronize operations across axes using the same
start_offsetvalues - Use different frequencies on different axes to create complex, layered sensations
Cause: Sampling aliasing - frequency matches sample rate Solution: Change frequency from multiples of 10 (e.g., 10→11, 20→21, 60→65)
Cause: Amplitude too small or values getting clipped Solution: Increase amplitude, or use volume headroom feature to create space for additive effects
Cause: Effects pushing values out of valid 0.0-1.0 range Solution:
- Reduce amplitude or offset values
- Use the volume headroom setting in the UI (shifts baseline down before applying effects)
- Switch from additive to overwrite mode if appropriate
Cause: Missing or insufficient ramp envelopes
Solution: Add or increase ramp_in_ms and ramp_out_ms values
Cause: Specified axis files don't exist
Solution: Ensure all comma-separated axis files exist (e.g., both video.volume.funscript and video.volume-prostate.funscript must exist to use axis: volume,volume-prostate)