3. How the Plugin Works
This section describes the physical modeling algorithms in detail for readers interested in the theory behind the sound.
Architecture Overview
The plugin is built as a hierarchy of interconnected models:
Wind Model (shared)
|
v
12x Vortex Exciter --> 12x String Model --> Stereo Mixer --> Output
^ ^
| |
Strouhal matching Digital waveguide
| |
Lock-in detection Loop filter + feedback
|
Sympathetic coupling <--+
(between all |
12 strings) -----+
Each component models a distinct aspect of the physics.
Wind Simulation
Real wind is not steady -- it gusts and lulls at multiple timescales simultaneously. The plugin's wind model captures this by layering three independent noise generators:
| Layer | Update Rate | Weight | Physical Analogue |
|---|---|---|---|
| Slow | 0.3 Hz | 50% | Large-scale gusts (3-5 second cycles) |
| Medium | 3.0 Hz | 30% | Moment-to-moment pressure variation |
| Fast | 15.0 Hz | 20% | Turbulence and micro-fluctuations |
Each layer generates random target values and smoothly interpolates between them using a lowpass filter. The three layers are combined as a weighted sum and applied as a proportional variation to the base wind speed. The slow layer is scaled by the Gust control; the medium and fast layers are scaled by the Turbulence parameter. The two scalings are independent, so you can choose any mix of slow swelling and high-frequency jitter.
The Wind Strength knob maps to wind speed through an exponential curve:
baseSpeed = 15.0 * (exp(4 * strength) - 1) / (exp(4) - 1)
This concentrates most of the knob's travel (0 to 0.8) in the musically useful range of 0 to 3 m/s, while reserving the top portion for extreme speeds up to 20 m/s. The Randomness knob scales the variation depth.
The slow layer also drives the gust envelope, a slowly undulating value between 0 and 1 that modulates the wind excitation drive into the strings when the Breath control is above zero. Because it shapes the drive rather than the output, a lull lets already-ringing strings decay at their own rate (and leaves sympathetic tails untouched), producing the characteristic slow breathing of a real aeolian harp.
Vortex Excitation Model
Each of the 12 strings has its own vortex exciter that models the von Karman vortex street phenomenon.
Strouhal Frequency
When wind at speed v flows past a string of diameter d, vortices are shed at a frequency determined by the Strouhal relationship:
f_vortex = 0.2 * v / d
The Strouhal number (0.2) is an empirically established constant for cylindrical objects in the Reynolds number range relevant to musical strings.
Harmonic Lock-In
A string will only vibrate when the vortex shedding frequency falls close to one of its natural harmonics. The exciter scans up to 48 harmonics of each string's fundamental frequency and evaluates how closely the vortex frequency matches each one using a Gaussian proximity function:
proximity = |f_vortex - f_harmonic| / f_harmonic
match = exp(-proximity^2 / (2 * sigma^2))
The width parameter sigma represents the lock-in bandwidth. It depends on string length:
sigma = 0.18 * (0.90 / L)^1.5
Where L is the string length in meters and 0.90 m is the reference length. At the 90 cm reference this yields a fractional FWHM of roughly 42% of the harmonic frequency, matching wind-tunnel measurements (Spalding 2021) showing each harmonic remains audible across a 2--3 m/s band of wind speed. Longer strings (under higher tension at fixed pitch) have higher mechanical Q and therefore narrower lock-in windows; shorter strings have wider windows.
Each harmonic match is weighted by a low-mode-suppressed factor:
weight(n) = (1 - exp(-(n / 2.5)^2)) / n
This replaces the naive 1/n rolloff to reflect a key observation about real aeolian harps: the fundamental almost never sounds, the 2nd harmonic appears only as a faint undertone, and the 3rd harmonic is typically the lowest mode heard. The exponential factor strongly attenuates n=1 and n=2, leaving the 3rd harmonic as the peak-weighted mode while preserving 1/n falloff above that.
To prevent unison strings (e.g. paired courses tuned to the same pitch) from locking onto the same harmonic and sounding identical, each exciter applies a small (±0.7%) per-instance jitter to its candidate harmonic frequencies. This jitter is too small to be audible as detuning on a single string but is large enough that two unison strings will often select different harmonics, naturally forming a sparse chord rather than a flat unison -- another effect documented in Spalding (2021). The jitter is seeded deterministically from the string index, so behaviour is repeatable within a session.
The harmonic with the strongest weighted match determines the excitation frequency and energy level for that string.
Excitation Signal
The exciter generates band-limited noise by passing white noise through a state-variable bandpass filter (Q = 8.0) centered on the matched harmonic frequency. The filter output is scaled by the match energy and a fixed amplitude factor (0.15) to produce the excitation signal fed into the string model. Energy transitions are smoothed over approximately 50 ms to prevent discontinuities as harmonics shift.
Digital Waveguide String Model
Each string is implemented as a Karplus-Strong-style digital waveguide -- a technique developed by Kevin Karplus and Alex Strong (1983) and later formalized by Julius O. Smith III as part of the digital waveguide synthesis framework.
Delay Line
The core of the model is a circular delay line whose length determines the fundamental frequency:
delaySamples = sampleRate / fundamentalHz
A signal injected into the delay line circulates once per period, producing a sustained tone at the fundamental frequency. The delay line is sized when audio starts, from the actual sample rate and the lowest reachable note, so the longest loop always fits — every note stays on pitch at 44.1 kHz through 192 kHz.
Loop Filter
A first-order lowpass filter in the feedback path models frequency-dependent energy loss. Higher frequencies lose energy faster with each trip around the delay line, just as they do on real strings. The cutoff frequency is controlled by the Brightness parameter:
cutoff = 1000 * 8 ^ brightness Hz (1 kHz at 0, 8 kHz at 1)
The map is exponential so that the perceived change in brightness spreads evenly across the knob travel rather than bunching in the bottom third.
This is further modified by the string material: Metal strings shift the cutoff 30% higher, Nylon shifts it 50% lower, and Gut shifts it 30% lower. The material multiplier can push the effective brightness above 1.0 (Metal at full Bright reaches ~1.3), lifting the cutoff past 8 kHz up to a 12 kHz (or Nyquist) ceiling — so the entire travel of the Bright knob stays active even on Metal.
Feedback and Ring Time
The feedback coefficient determines how long the string rings. It is derived from the desired T60 time (the time for the signal to decay by 60 dB). Damp is the single control on this axis -- it sets the effective damping value directly, scaled per material. (A separate Decay control once acted as a modifier on Damp, but it was removed because it moved the same axis in the same direction as Damp -- its measured ring-time span across the whole knob was below audibility -- so the two were redundant. Damp's default was re-centred from 0.3 to 0.225 so the out-of-the-box sustain is identical to before.)
effectiveDamping = max(0.15, damping / materialDecayScale)
T60_effective = T60_base * (L / 0.90) * (1 - 0.8 * effectiveDamping)
feedback = 10^(-3 * N / (T60_effective * sampleRate))
Where N is the delay line length in samples, L is the string length in meters, and T60_base is a per-string constant (ranging from 3.0 to 6.0 seconds depending on the string). The floor of 0.15 on effectiveDamping ensures strings always lose energy regardless of how Damp is set -- infinite sustain is not possible.
The material also affects decay: Metal strings have a decay scale of 1.4 (40% longer sustain), Gut is the reference at 1.0, and Nylon has a scale of 0.7 (30% shorter sustain).
DC Blocking
A high-pass filter removes any DC offset that accumulates in the feedback loop:
y[n] = x[n] - x[n-1] + 0.9995 * y[n-1]
The coefficient 0.9995 sets the cutoff at approximately 2 Hz, removing only true DC while preserving all audible content.
Sympathetic Resonance
On a real multi-stringed instrument, vibrating strings transfer energy to other strings through the shared bridge and soundboard. The plugin models this with a coupling matrix that tracks the current tuning: retuning a string re-derives its coupling to every other string from the new pitch, so two strings brought to unison couple strongly and strings detuned apart couple weakly (issue #694).
Coupling Matrix
For every pair of strings, the algorithm examines the first 24 harmonics of each string and identifies matches within 1% frequency tolerance. Each match is weighted by 1/(n_i * n_j), where n_i and n_j are the harmonic numbers, reflecting the principle that lower harmonics couple more efficiently. The matrix is normalized so the strongest off-diagonal coupling equals 1.0.
Strings with simple frequency ratios (octaves, fifths) have many matching harmonics and strong coupling. Strings with complex ratios have weaker coupling.
Runtime Coupling
During audio processing, each string receives a small excitation derived from the output of all other strings, weighted by the coupling matrix:
sympatheticExcitation[i] = couplingGain * sum(coupling[j][i] * output[j]) for all j != i
The coupling gain follows a squared perceptual curve (sympathetic parameter squared * 0.08), so the bottom of the knob stays subtle while the top reaches an audible level; at zero the knob fully disables coupling. Each string's injected energy is additionally clamped to a small ceiling to prevent feedback runaway across 12 mutually coupled strings.
Because the coupling now tracks the actual tuning, a strongly coherent voicing (many strings at unisons, octaves, or a tight chord) sums far more energy back into each string than a spread tuning does. To keep the Sympathy knob safe across every tuning, the effective coupling for each string is bounded by its total incoming coupling weight (issue #705): the more strings pile onto shared pitches, the lower the per-string coupling gain is allowed to climb before it holds steady. In practice this means the top of the Sympathy knob no longer increases richness on an already-coherent tuning — it has reached the stable ceiling — but the knob can never drive the coupled strings into self-oscillation, whatever notes you tune them to.
Stereo Imaging
The 12 strings are distributed across the stereo field using constant-power panning. Each string is assigned a fixed position from left to right (string 0 at the leftmost position, string 11 at the rightmost). The Stereo Width parameter interpolates between full-center (mono) and full-spread:
pan_L = cos(position * pi/2)
pan_R = sin(position * pi/2)
At stereo width 0, all strings collapse to the center. At width 1, the strings span the full image with constant total power.
Output Stage
The outputs of all 12 strings are summed (with octave partners scaled by the Mix parameter), normalized by 1/sqrt(12), scaled by the fixed source drive, and passed through a soft tanh saturator that provides smooth, warm limiting at high levels. (The gust envelope (Breath) is applied earlier, to the wind excitation drive, not at this output stage.)
Material Modifiers
The three material presets adjust the DSP parameters multiplicatively:
| Property | Gut | Metal | Nylon |
|---|---|---|---|
| Brightness scale | 0.7x | 1.3x | 0.5x |
| Decay scale | 1.0x | 1.4x | 0.7x |
| Excitation scale | 0.85x | 1.0x | 0.7x |
These multipliers interact with all the relevant DSP stages to create distinct tonal characters from the same underlying physical model.
Summary of Key Constants
| Constant | Value | Role |
|---|---|---|
| Strouhal number | 0.2 | Vortex shedding frequency ratio |
| Base lock-in sigma | 0.18 | Resonance bandwidth at 90 cm (~42% FWHM) |
| Per-string harmonic jitter | ±0.7% | Detunes candidate harmonics so unison strings spread |
| Bandpass Q | 8.0 | Excitation filter sharpness |
| Max harmonics scanned | 48 | Upper limit for harmonic matching |
| Max delay line | sized per sample rate | Sized in prepare() so the lowest note fits at any rate (44.1–192 kHz) |
| DC block coefficient | 0.9995 | High-pass pole (~2 Hz cutoff) |
| Coupling gain scale | sympathetic squared * 0.08 (per-string loop gain capped at 0.011; injection clamped to +/-0.05) | Perceptual curve with a stability-derived loop-gain cap that keeps any tuning safe |
| Normalization | 1/sqrt(12) | 12-string level balance |
Aeolian Wind Drone VST -- Copyright 2026 Daihamu. All rights reserved.