logo

MilkDrop Preset Authoring Guide

Author: Ryan Geiss
Original: geisswerks.com/milkdrop/milkdrop_preset_authoring.html

This guide is reproduced here with credit to Ryan Geiss and for the convenience of the MilkDrop community. For the most up-to-date version, please refer to the original document.


Table of Contents

  1. About Presets
  2. Preset Authoring — Basic
  3. Preset Authoring — Advanced

1. About Presets

When you watch MilkDrop, you are watching a series of presets. Each one has its own look and feel, draws the sound waves in a particular way, and has certain motions to it. After some time, you will see a short blend transition, and then you will be watching a new preset.

A single preset is a collection of parameters that tell MilkDrop how to draw the wave, how to warp the image around, and so on. MilkDrop ships with over 100 built-in presets, each one having a distinct look and feel to it.

Using MilkDrop’s built-in “preset-editing menu” (the M key), you can edit presets on the fly, on-screen, from within the program. You can make slight adjustments to existing presets, then save over them; or you can change lots of things so the preset doesn’t look anything like the original, and then save it under a new name. You can even write insane new mathematical equations, of your own imagination, into your preset files and come up with things that MilkDrop has never done before!

Each preset is saved as a file with the .milk extension, so you can easily send them to your friends or post them on the web.


2. Preset Authoring — Basic

You can edit the properties of the current preset by hitting M, which brings up the “preset-editing menu”. From this menu you can use the up and down arrow keys to select an item. Press the RIGHT arrow key to move forward through the menu and select the item (you can also hit SPACE or RETURN); press the LEFT arrow key to go back to the previous menu.

Pressing M while the menu is already showing will hide the menu; pressing ESCAPE will do the same thing. Press M again to bring the menu back.

Once you’ve reached an item on the menu whose value can be edited, use the UP and DOWN arrow keys to increase or decrease its value, respectively. Changes will register immediately. Use PAGE UP and PAGE DOWN to increase the value more quickly. Hold down SHIFT and use the UP/DOWN arrow keys to change the value very slowly. Hit RETURN to keep the new value, or ESC to abort the change.

If the item you’re editing is a text string, you can use the arrow keys to move around. The Insert key can be used to toggle between insert and overtype modes. You can hold shift and use the arrow keys (home, end, left, right) to make a selection, which will be identified by brackets []. You can then use CTRL-C or CTRL-X to copy or cut text. CTRL-P pastes. When finished editing, hit RETURN to keep the new string, or ESC to abort the change.

You’ll want to get into the habit of using SCROLL LOCK whenever you’re making changes to a preset that you intend to save; otherwise, MilkDrop is sure to move you along to a new (random) preset, over time.

Hotkeys

Motion:

KeyAction
i / Izoom in / out
[ / ]push motion left / right (dx)
{ / }push motion up / down (dy)
< / >rotate left / right (rot)
o / Oshrink / grow warp amplitude

Waveform:

KeyAction
Wcycle through waveforms
j / Jscale waveform down / up
e / Emake waveform more transparent / more solid

Brightness (MilkDrop 1 presets only):

KeyAction
g / Gdecrease / increase gamma

Video Echo (MilkDrop 1 presets only):

KeyAction
q / Qscale 2nd layer down / up
Fflip 2nd layer (cycles 4 orientations)

3. Preset Authoring — Advanced

This section describes how to use the per-frame and per-vertex equations to develop unique new presets.


a. Per-Frame Equations

When you hit M to show the preset-editing menu, all of the properties that make up the preset are there. The values you can specify here (zoom amount, rotation amount, wave color, etc.) are all static values — they don’t change in time.

However, presets get far more interesting if you can animate these parameters. For example, if you could make the zoom amount oscillate between 0.9 and 1.1 over time, the image would cyclically zoom in and out.

You can do this by writing per-frame equations. These are executed once per frame:

zoom = zoom + 0.1*sin(time);

This would make the zoom amount oscillate between 0.9 and 1.1 over time. The equation says: “take the static value of zoom, then replace it with that value, plus some variation.” This equation cycles every 6.28 seconds (since sin()‘s period is 2π). To cycle every 2 seconds:

zoom = zoom + 0.1*sin(time*3.14);

To make waveform color vary through time:

wave_r = wave_r + 0.5*sin(time*1.13);
wave_g = wave_g + 0.5*sin(time*1.23);
wave_b = wave_b + 0.5*sin(time*1.33);

Stagger the frequencies so the R, G, and B components cycle at different rates, to avoid a greyscale wave.

Per-Frame Variable Reference
NameWritable?RangeDescription
zoomyes>0inward/outward motion. 0.9=zoom out 10%, 1.0=none, 1.1=zoom in 10%
zoomexpyes>0curvature of the zoom; 1=normal
rotyesanyrotation. 0=none, 0.1=CCW, -0.1=CW
warpyes>0warping magnitude; 0=none, 1=normal
cxyes0..1center of rotation/stretching, horizontal. 0=left, 0.5=center, 1=right
cyyes0..1center of rotation/stretching, vertical. 0=top, 0.5=center, 1=bottom
dxyesanyhorizontal motion. -0.01=left, 0=none, 0.01=right
dyyesanyvertical motion. -0.01=up, 0=none, 0.01=down
sxyes>0horizontal stretching; 0.99=shrink 1%, 1=normal
syyes>0vertical stretching; 0.99=shrink 1%, 1=normal
wave_modeyes0-7waveform type
wave_xyes0..1waveform position X. 0=left, 0.5=center, 1=right
wave_yyes0..1waveform position Y. 0=bottom, 0.5=center, 1=top
wave_ryes0..1red color in wave
wave_gyes0..1green color in wave
wave_byes0..1blue color in wave
wave_ayes0..1opacity of wave. 0=transparent, 1=opaque
wave_mysteryyes-1..1does different things per waveform type
wave_usedotsyes0/1draw wave as dots instead of lines
wave_thickyes0/1double thickness
wave_additiveyes0/1additive drawing, saturating toward white
wave_brightenyes0/1scale R/G/B until at least one reaches 1.0
ob_sizeyes0..0.5outer border thickness
ob_r, ob_g, ob_byes0..1outer border color
ob_ayes0..1outer border opacity
ib_sizeyes0..0.5inner border thickness
ib_r, ib_g, ib_byes0..1inner border color
ib_ayes0..1inner border opacity
mv_r, mv_g, mv_byes0..1motion vector color
mv_ayes0..1motion vector opacity
mv_xyes0..64number of motion vectors in X
mv_yyes0..48number of motion vectors in Y
mv_lyes0..5motion vector trail length
mv_dx, mv_dyyes-1..1motion vector placement offset
decayyes0..1fade to black. 1=no fade, 0.9=strong, 0.98=recommended
gammayes>0brightness. 1=normal, 2=double
echo_zoomyes>0size of 2nd graphics layer
echo_alphayes>0opacity of 2nd layer. 0=off, 0.5=half, 1=opaque
echo_orientyes0-3orientation of 2nd layer. 0=normal, 1=flip X, 2=flip Y, 3=both
darken_centeryes0/1dims center to prevent overbright
wrapyes0/1screen element wrapping
invertyes0/1invert colors
brightenyes0/1brighten dark parts (square root filter)
darkenyes0/1darken bright parts (squaring filter)
solarizeyes0/1emphasize mid-range colors
monitoryesanydebug value shown with N key
timeNO>0seconds since MilkDrop started
fpsNO>0current framerate
frameNOanyframe count since start
progressNO0..1progress through preset (0=just loaded, 1=about to end)
bassNO>0current bass level. 1=normal
midNO>0current mid level
trebNO>0current treble level
bass_attNO>0attenuated bass (damped in time)
mid_attNO>0attenuated mid
treb_attNO>0attenuated treble
meshxNO8-128mesh size X
meshyNO6-96mesh size Y
pixelsxNO16-4096window width in pixels
pixelsyNO16-4096window height in pixels
aspectxNO>0aspect ratio multiplier for X
aspectyNO>0aspect ratio multiplier for Y
q1q32yesanypass values between variable pools (diagram)

You can also make up to 30 of your own variables:

my_volume = (bass + mid + treb)/3;
zoom = zoom + 0.1*(my_volume - 1);

Note: Custom variables do NOT carry over from per-frame to per-vertex equations. Use q1q32 to bridge the gap.


b. Per-Vertex Equations

What if you wanted to vary a parameter differently for different locations on the screen? For example, making pixels far from center zoom more for a perspective effect:

zoom = zoom + rad*0.1;

Where rad is the distance of the pixel from the center of the screen (0 at center, 1 at corners).

Per-Vertex Exclusive Variables
NameWritable?RangeDescription
xNO0..1X position. 0=left, 0.5=center, 1=right
yNO0..1Y position. 0=top, 0.5=center, 1=bottom
radNO0..1distance from center. 0=center, 1=corners
angNO0..6.28angle from center. 0=right, π/2=above, π=left, 3π/2=below

All per-frame writable variables (zoom, rot, warp, cx, cy, dx, dy, sx, sy) plus all read-only variables (time, fps, bass, etc.) and q1q32 are also available.

Performance maxim: “If a per-vertex equation doesn’t use at least one of the variables { x, y, rad, ang }, then it should actually be a per-frame equation.”

The per-vertex equations are actually computed only at mesh vertices, then interpolated across the space between them. The “mesh size” option defines how many evaluation points there are.


c. Variable Pools, Declaring Your Own Variables, Persistence

Declaring your own variables is simple:

billy = 5.3;

There are three variable pools in MilkDrop:

  1. Preset init code + preset per-frame code
  2. Custom wave init + custom wave per-frame code
  3. Custom shape init + custom shape per-frame code

Within a pool, user-defined variables persist from frame to frame. But you can’t read a variable from another pool — use q1q32 to bridge between pools.

Per-vertex code and custom wave per-point code use scratch variables only — they don’t persist meaningfully from vertex to vertex or point to point.


d. Preset Init Code; Q Variables

The preset initialization code runs once when a preset is loaded. It does two things:

  1. Sets initial values for user-defined variables
  2. Sets default (“sticky”) values for q1q32

Whatever values q1q32 have after the init code become the starting values for each frame. Per-frame code can modify them, and those modifications propagate to per-vertex code and shaders — but are reset each frame.

Q Variable Flow Diagram

Side note: When you edit init code and apply with CTRL+ENTER, it re-executes immediately. But editing per-frame/per-vertex code does NOT re-execute the init code — you’ll have to save and reload the preset.


e. Custom Shapes & Waves

A preset can have up to 4 custom shapes and 4 custom waves.

Custom Shapes draw an n-sided polygon (3–100 sides) anywhere on the screen, at any angle, size, color, and opacity. You can map the previous frame’s image onto the shape for effects like realtime hardware fractals. Each shape can be instanced up to 1024 times per frame.

Custom Waves draw the waveform or spectrum however you want, with per-frame and per-point control over placement and color.

T Variables (t1t8): Similar to Q variables, but only for custom waves and shapes — they bridge between wave/shape init code and per-frame code. See the T variable diagram.

Custom Shape Per-Frame Variables
NameWritable?RangeDescription
num_instno1-1024total instances to draw
instanceno0..num_inst-1current instance number
sidesyes3-100number of sides
thickyes0/1bold border
additiveyes0/1additive blending
xyes0..1X position
yyes0..1Y position (0=bottom, 1=top)
radyes0+radius
angyes0..6.28rotation angle
texturedyes0/1texture with previous frame’s image
tex_zoomyes>0texture zoom
tex_angyes0..6.28texture rotation angle
r, g, b, ayes0..1center color & opacity
r2, g2, b2, a2yes0..1edge color & opacity
border_r, border_g, border_b, border_ayes0..1border color & opacity
Custom Wave Per-Frame Variables
NameWritable?RangeDescription
r, g, b, ayes0..1base color & opacity
samplesyes0-512number of samples
Custom Wave Per-Point Variables
NameWritable?RangeDescription
xyes0..1X position (0=left, 1=right)
yyes0..1Y position (0=bottom, 1=top)
sampleno0..1progress through waveform. 0=first, 1=last
value1noanyLeft audio channel sample
value2noanyRight audio channel sample
r, g, b, ayes0..1color & opacity at this point

All pools also have access to time, fps, frame, progress, audio variables, q1q32, and t1t8.


f. Pixel Shaders

MilkDrop 2 supports programmable pixel shaders. Each preset has two shaders:

  • Warp shader — warps the image from frame to frame (effects get “baked in” and persist)
  • Composite shader — draws the frame to screen (display only, doesn’t affect next frame)

Edit them via the M menu, or press F9 while editing for the onscreen quick reference.

Shader Models
  • MilkDrop 1: Fixed-function graphics only (no shaders)
  • Shader Model 2.0: 64 instruction limit per shader
  • Shader Model 3.0: Virtually unlimited instructions (not all GPUs)
Warp Shader Example
shader_body
{
    // sample the previous frame (UV is warped by per-vertex equations)
    ret = tex2D( sampler_main, uv ).xyz;

    // darken over time
    ret *= 0.97;
}

The UV coordinates drive the motion — they’re computed from per-vertex equations and interpolated across the screen.

Composite Shader Example
shader_body
{
    // sample the internal canvas (uv is undistorted here)
    ret = tex2D(sampler_main, uv).xyz;

    // make it a little "overbright"
    ret *= 1.8;
}
Data Types
TypeDescription
float, float2, float3, float4Full-precision floating point
half, half2, half3, half4Half-precision (faster on some hardware)
float2x2, float3x3, float4x3Transformation matrices
Swizzle Operators

You can reorder components using .xyzw:

float4 delta = float4(5,6,7,8);
delta.wywy  // -> float4(8,6,8,6)
Intrinsic Instructions

Math:

FunctionDescription
abs(a)Absolute value
frac(a)Fractional part
floor(a)Integer part (single floats only)
saturate(a)Clamp to [0..1] — often FREE
max(a,b)Greater of each component
min(a,b)Lesser of each component
sqrt(a)Square root
pow(a,b)a^b
exp(a)2^a
log(a)log2(a)
lerp(a,b,c)Linear interpolate: a + c*(b-a)
dot(a,b)Dot product (returns single float)
lum(a)Convert float3 color to luminance
length(a)Vector length
normalize(a)Normalize to unit length

Texture:

FunctionDescription
tex2D(sampler, uv)Sample 2D texture at float2 UV → float4
tex3D(sampler, uvw)Sample 3D texture at float3 → float4
GetBlur1(uv)Slightly blurred main texture → float3
GetBlur2(uv)More blurred → float3
GetBlur3(uv)Very blurred → float3

Slow operations (use sparingly):

FunctionDescription
sin(a)~8 instructions
cos(a)~8 instructions
atan2(y,x)Arctangent of y/x — very slow
mul(a,b)Matrix × vector multiply
cross(a,b)Cross product (float3 only)
Per-Vertex Shader Inputs

Warp shader:

InputDescription
float2 uvWarped UV coords ~[0..1]
float2 uv_origOriginal (un-warped) UV coords [0..1]
float radRadius from center [0..1]
float angAngle from center [0..2π]

Composite shader:

InputDescription
float2 uvUn-warped UV coords
float radRadius from center
float angAngle from center
float3 hue_shaderColor varying across screen (legacy hue shader)
Per-Frame Shader Inputs
float4 rand_preset;  // 4 random floats [0..1], per preset
float4 rand_frame;   // 4 random floats [0..1], per frame
float  time;         // seconds since preset start (wraps at 10,000)
float  fps;          // framerate
float  frame;        // frame number
float  progress;     // progress through preset [0..1]
float  bass, mid, treb, vol;      // immediate audio
float  bass_att, mid_att, treb_att, vol_att;  // attenuated audio
float4 aspect;       // .xy aspect multiplier, .zw inverse
float4 texsize;      // .xy = (w,h); .zw = (1/w, 1/h)
float4 slow_roam_cos, slow_roam_sin;  // slow-varying [0..1]
float4 roam_cos, roam_sin;            // faster-varying [0..1]
float  q1 ... q32;  // per-frame equation outputs
float4 _qa ... _qh; // q1-q32 grouped into float4s

Plus rotation matrices: rot_s1rot_s4 (static), rot_d1rot_d4 (slow), rot_f1rot_f4 (fast), rot_vf1rot_vf4 (very fast), rot_uf1rot_uf4 (ultra fast), rot_rand1rot_rand4 (random every frame).

Texture Sampling

Main texture samplers:

SamplerFilteringOutside [0..1]
sampler_main (or sampler_fw_main)bilinearwrap
sampler_fc_mainbilinearclamp
sampler_pw_mainpointwrap
sampler_pc_mainpointclamp
Noise Textures
NameDimsPixelsQuality
noise_lq2D256×256low
noise_lq_lite2D32×32low
noise_mq2D64×64medium
noise_hq2D32×32high
noisevol_lq3D32³low
noisevol_hq3Dhigh

Use tex2D for 2D noise, tex3D for 3D noise. For 1:1 pixel mapping:

float4 texsize_noise_lq; // declare above shader_body
// In shader_body:
float4 noiseVal = tex2D(sampler_noise_lq,
    uv_orig * texsize.xy * texsize_noise_lq.zw + rand_frame.xy);
Reading Textures from Disk

Save textures to milkdrop2\textures\. Then in your shader:

sampler sampler_billy;        // loads billy.jpg (or .dds, .png, .tga, .bmp)
float4 texsize_billy;         // .xy = (w,h); .zw = (1/w, 1/h)

shader_body
{
    float3 mypixel = tex2D(sampler_billy, uv).xyz;
}

Supported formats: jpg, dds, png, tga, bmp, dib.

For random textures, use rand00 through rand15 as the filename. For random subsets, append a prefix: sampler sampler_rand02_smalltiled.

Cool Shader Tricks

Auto center darkening:

ret *= 0.97 + 0.03*saturate( length(uv - uv_orig)*200 );

Soft max (smoother than max(a,b), inputs must be [0..1]):

a + b - a*b

Error diffusion dithering:

float2 uv_noise = uv_orig*texsize.xy*texsize_noise_lq.zw + rand_frame.xy;
half4 noiseVal = tex2D(sampler_noise_lq, uv_noise);
ret += (noiseVal.xyz*2-1) * 0.01;

Best darkening approach:

ret = (ret - 0.002)*0.99;

g. Quality Assurance

  1. Keep presets fast. Division is 11× slower than multiplication — pre-divide common values. Move per-vertex computations that don’t use {x, y, rad, ang} into per-frame code.

  2. Design at default mesh size. Check that presets look correct at the default setting before distributing.

  3. Design in 32-bit color mode for standard brightness levels.

  4. Don’t underestimate dx and dy. Some of the best presets are based purely on manual dx/dy control — all other effects (zoom, warp, rot) are abstractions that can be simulated with {x, y, rad, ang} and {dx, dy}.

  5. Test progress usage with various “Time Between Auto Preset Changes” settings.

  6. Shader guidelines:

    • Use small (≤256×256) textures, saved as JPG 95%
    • Avoid if statements
    • Avoid massive zoom-outs of textures (thrashes texture cache)
    • Minimize sin()/cos() — precompute in per-frame equations and pass via q1q32
    • Offload per-pixel-constant calculations to per-frame equations
    • Test in both square and widescreen windows

h. Debugging

Use the variable monitoring feature:

  1. Press N to show the monitor value
  2. In per-frame equations, add: monitor = x; (where x is any variable or expression)
  3. Press CTRL+ENTER — the value appears in the upper-right corner

Note: Monitoring only works for per-frame equations, not per-vertex.


i. Function Reference

For preset init, per-frame, and per-vertex equations (NOT for pixel shaders):

Use semicolons (;) to delimit statements. Use parentheses for precedence.

Operators: = assign, + - * / arithmetic, | bitwise or, & bitwise and, % modulo

Functions:

FunctionDescription
int(x)Integer value (rounds toward zero)
abs(x)Absolute value
sin(x)Sine (radians)
cos(x)Cosine
tan(x)Tangent
asin(x)Arcsine
acos(x)Arccosine
atan(x)Arctangent
sqr(x)Square
sqrt(x)Square root
pow(x,y)x to the power of y
log(x)Natural log (base e)
log10(x)Log base 10
sign(x)Sign of x, or 0
min(x,y)Smallest value
max(x,y)Greatest value
sigmoid(x,y)Sigmoid function (y=constraint)
rand(x)Random integer modulo x
bor(x,y)Boolean OR (1 if either ≠ 0)
bnot(x)Boolean NOT (1 if x=0)
if(cond,a,b)If cond ≠ 0 returns a, else b
equal(x,y)1 if x=y, else 0
above(x,y)1 if x>y, else 0
below(x,y)1 if x<y, else 0

This guide was originally written by Ryan Geiss. Visit the original document for the most current version.