Phase Modulation Matrix — User Guide
Layered sinusoidal time displacement: each processing layer adds a time-shifted copy of the current signal, using a different modulation rate and depth, then passes the accumulated result into the next layer.
What this does
Phase Modulation Matrix creates chorus-, vibrato-, and phaser-like motion by repeatedly adding sinusoidally displaced reads of the signal to itself. The selected Sound is copied, then processed through a cascade of modulation layers. Each layer uses its own modulation frequency, depth, displaced-tap gain, and a frozen snapshot of the signal as it exists at the start of that layer.
What “phase modulation” means here
The title does not describe canonical oscillator phase-modulation synthesis. The script does not generate a sinusoidal carrier and alter its oscillator phase. Instead, it performs time-domain variable-delay / sample-displacement processing:
The displaced sample is read by continuous Sound-time interpolation and added to the current signal. Because the sinusoid swings positive and negative, the read path moves both later and earlier than the unshifted time position. This is an offline bidirectional time displacement, not a causal delay line.
Quick start
- Select exactly one Sound object.
- Run
Phase_Modulation_Matrix.praat. - Choose Default, Subtle Chorus, Deep Phase Sweep, Vibrato / Whirl, or Custom.
- Choose whether the base carrier value is fixed or randomly drawn from a range.
- Choose duration-relative depth or a fixed depth in milliseconds.
- Set output peak, visualization, and playback options.
- Run the script. The result is named
<source>_phasemod.
Use_fixed_carrier, Use_fixed_ms_depth, Fixed_depth_ms, Scale_peak, visualization, or playback.Layer engine
At the beginning of every layer, the current accumulated Sound is copied to a temporary snapshot. All displaced reads for that layer come from this frozen snapshot rather than from samples already modified earlier in the same Formula pass.
This makes each individual layer feed-forward. However, the layers form a cascade: layer 2 receives the completed output of layer 1, layer 3 receives the completed output of layer 2, and so on.
Boundary behavior
If the sinusoidal read position falls before the Sound start or after its end, that layer contributes no displaced tap at that time; only the current dry/accumulated sample remains. No wrapping, reflection, or zero-valued wet sample is mixed in.
Carrier value & per-layer modulation rates
The form retains the historical name Carrier_freq, but operationally this value is the base modulation rate from which each layer's sinusoidal displacement rate is derived.
Base value
If Use_fixed_carrier is enabled:
Otherwise, one value is drawn uniformly in Hz at the start of the run:
The same selected carrier value is then used for all layers in that run.
Layer rates
These values are true cycles per second. For example, a 0.3 Hz carrier produces layer rates of 0.6, 0.9, 1.2, 1.5 Hz, and so on.
Modulation depth
Depth is always converted to seconds before the displaced read is calculated.
Fixed-ms depth
When Use_fixed_ms_depth is enabled, every layer receives the same time excursion:
Duration-relative depth — default
When fixed-ms depth is off, depth is a fraction of the entire source duration:
Because the denominator increases with layer number, later layers normally use shallower excursions. With the Default preset, for example:
“Feedback” & layer gain
The public parameter Feedback_base retains its historical name, but there is no within-layer feedback loop in v0.5. It controls the gain of the feed-forward displaced tap:
The second gain term falls linearly with layer number and is clamped at zero:
The actual displaced contribution is:
So early layers generally contribute more strongly; sufficiently high layer numbers can become dry-only when layerGain reaches zero.
Presets
| Preset | Layers | Carrier range | Fixed carrier | Depth base / increment | Feedback | Layer gain base / rate |
|---|---|---|---|---|---|---|
| Default (balanced) | 5 | 0.10–0.50 Hz | 0.30 Hz | 8 / 2 | 0.70 | 1.10 / 0.10 |
| Subtle Chorus | 3 | 0.05–0.20 Hz | 0.15 Hz | 10 / 1 | 0.40 | 1.05 / 0.05 |
| Deep Phase Sweep | 6 | 0.10–0.40 Hz | 0.28 Hz | 6 / 2 | 0.80 | 1.15 / 0.12 |
| Vibrato / Whirl | 7 | 0.20–0.80 Hz | 0.45 Hz | 5 / 3 | 0.90 | 1.20 / 0.15 |
| Custom form defaults | 5 | 0.10–0.50 Hz | 0.30 Hz | 8 / 2 | 0.70 | 1.10 / 0.10 |
Fixed_depth_ms. If fixed-ms depth is enabled, the selected preset still changes its other layer/frequency/gain values, but the user's current fixed depth remains in force.Parameters & limits
| Parameter | Default | Exact role / validation |
|---|---|---|
| Preset | Default | Four named presets plus Custom. |
| Modulation_layers | 5 | Number of cascaded layers; 1–128. |
| Carrier_freq_min | 0.1 Hz | Lower bound of random base-rate draw; must be >0 and ≤ maximum. |
| Carrier_freq_max | 0.5 Hz | Upper bound of random base-rate draw; must be >0. |
| Use_fixed_carrier | Off | Off = one uniform random draw per run; On = use Fixed_carrier_freq. |
| Fixed_carrier_freq | 0.3 Hz | Positive fixed base rate. |
| Mod_depth_base | 8 | Positive denominator term for duration-relative depth. |
| Mod_depth_increment | 2 | Positive layer-dependent denominator increment. |
| Use_fixed_ms_depth | Off | Switch from duration-relative depth to one fixed depth for every layer. |
| Fixed_depth_ms | 20 ms | Positive displacement depth when fixed-ms mode is active. |
| Feedback_base | 0.7 | Positive base coefficient; actual feed-forward tap term is divided by layer number. |
| Layer_gain_base | 1.1 | Positive starting value for layer gain. |
| Layer_gain_rate | 0.1 | Positive amount subtracted per layer; effective layer gain is clamped at zero. |
| Scale_peak | 0.93 | Target final peak, >0 and ≤1. |
| Draw_visualization | On | Draw the v0.5 process view. |
| Play_result | On | Play the completed Sound. |
Level handling
Every successful layer adds a displaced copy to the already accumulated signal, so the unscaled result can exceed the source level substantially.
After all layers, the script measures the Sinc70 absolute extremum. If the result is non-silent, it calls:
Visualization
The v0.5 Picture view is organized around the actual time-displacement process:
- Input waveform — original source copy.
- Output waveform — processed result.
- Shared amplitude scale — Input and Output use the same vertical range for direct level comparison.
- Phase-displacement field — one lane per displayed modulation layer. The gray line is the unshifted read position; the amber sinusoid is the actual displaced read trajectory.
- Curve rate — follows the real layer modulation frequency
carrier × (layer+1). - Curve excursion — preserves each displayed layer's depth relative to the deepest displayed layer.
- Input / Output spectrograms — mono display folds, up to min(5 kHz, Nyquist).
- Summary strip — preset, layer count, carrier, depth mode, feedback, layer-gain law, and target peak.
More than eight layers
The displacement field shows at most eight lanes. If the processor uses more than eight layers, the visualization samples layer numbers across the complete 1…N range rather than drawing all 128 possible lanes.
Output behavior
- Name:
<source>_phasemod. - Duration: unchanged from the selected source.
- Time domain: preserved from the processing copy; only visualization copies are shifted to zero for display.
- Sample rate: preserved.
- Channel count: preserved.
- Final level: target peak normalization to
Scale_peakfor non-silent output. - Randomness: only the base carrier draw is random when fixed-carrier mode is off; no random seed is exposed.
The original Sound remains unchanged. Temporary layer snapshots and visualization copies are removed.