Phase-Space Composer — Attractor-Driven Event Montage — User Guide
Segments a source Sound into acoustic events, describes each event in a normalized feature space, generates a dynamical-system trajectory, and maps trajectory steps back to source events for multichannel-preserving montage.
What this does
Phase-Space Composer is an event-montage processor controlled by a dynamical trajectory. Praat first segments the selected Sound into sounding events. A Python stage extracts five acoustic descriptors per event, builds a 2D–5D state space, generates one trajectory point per requested output event, and chooses source events whose normalized feature positions match the trajectory under the active mapping rules.
The selected event sequence is returned to Praat as a plan. Praat then extracts those events from the original source Sound and assembles them with true overlap crossfades. The original source channel count is therefore preserved in the output.
What is phase-space composition here?
The phase space is not the waveform itself. It is a normalized acoustic-feature space. Source events are points in that space, while Hopf, Lorenz, Rössler, or LogisticMap supplies a deterministic trajectory through a space of the same dimensionality. Each trajectory step becomes a target for selecting one source event.
Processing chain:
Quick start
- Select exactly one Sound in Praat.
- Run
PhaseSpaceComposer.praat. The script looks forphase_space_compose.pyin the AudioToolspy/folder and then next to the script. - Choose an attractor. Lorenz is the default.
- Choose the state dimension. The default is 3D: centroid + flatness + flux.
- Choose a distance-weight preset. Uniform is the default.
- Set output event count, tabu length, temperature, and seed.
- Optionally add velocity alignment or feedback coupling.
- Adjust event segmentation and crossfade values if needed.
numpy, scipy, and soundfile. The Praat script probes these dependencies before analysis and stops if they are unavailable.
Event features and state space
Praat chooses one representative real source channel for analysis: the channel with the highest whole-file RMS. That same 1-based channel number is passed to Python, so segmentation and feature extraction use the same physical channel and avoid stereo fold-down cancellation.
| Feature | Implementation | Role |
|---|---|---|
| Centroid | Magnitude-weighted spectral centroid in Hz. | Spectral center / brightness proxy. |
| Flatness | Geometric mean divided by arithmetic mean of the mean magnitude spectrum, clipped to 0–1. | Tonal-to-noise-like spectral distribution. |
| Entropy | Shannon entropy of normalized spectral magnitudes, divided by log2(N). | Spectral distribution complexity. |
| Flux | Mean RMS frame-to-frame magnitude-spectrum difference. | Spectral change / transient-activity proxy. |
| RMS | Root-mean-square sample amplitude. | Signal energy descriptor. |
Analysis windowing
Python starts from a 1024-sample FFT window with 75% overlap. For short events the FFT size is repeatedly halved down toward 64 samples. Hann windowing is used. If an event is still shorter than the chosen FFT size, it is zero-padded to one frame.
State dimensions
Robust normalization
Each active feature is normalized independently: subtract its median, divide by its interquartile range, clamp the robust score to −3…+3, then map that range to 0…1. This limits the influence of extreme events while keeping all active dimensions on a common numerical scale.
Attractor types
Continuous systems are integrated with fourth-order Runge–Kutta after a fixed burn-in. Their coordinates are then adjusted to the requested state dimensionality and min–max normalized per dimension to 0…1.
| Attractor | Current implementation |
|---|---|
| Hopf | Hopf normal form with α=0.5, ω=2π and dt=0.05; 500 burn-in steps. Native 2D limit cycle; higher dimensions use deterministic smooth extensions. |
| Lorenz | σ=10, ρ=28, β=8/3, dt=0.02; 1000 burn-in steps. Native 3D system; dimensions are truncated or smoothly extended as needed. |
| Rössler | a=0.2, b=0.2, c=5.7, dt=0.05; 2000 burn-in steps. Native 3D system; dimensions are adjusted as above. |
| LogisticMap | x[n+1] = 3.9·x[n]·(1−x[n]), 500 burn-in iterations, with a D-dimensional time-delay embedding. Its initial value is seed-dependent. |
Trajectory → event mapping
What is the mapping distance?
The v1.4 Python engine uses a weighted RMS Euclidean position distance:
Because event coordinates and trajectory coordinates are normalized to 0…1, this distance is also bounded to the same approximate 0…1 scale as the velocity term.
Dimension weights
The five preset weights are always supplied in canonical order centroid, flatness, entropy, flux, rms. Python resolves them by feature name onto the dimensions actually active in 2D, 3D, 4D, or 5D. They are not positionally truncated.
Feedback coupling
From the second mapping step onward, coupling can pull the current trajectory target toward the previously selected event:
Coupling = 0 leaves the trajectory unchanged. Increasing it makes selection history influence the next target.
Velocity alignment
When Velocity_weight > 0, the mapper compares the trajectory's current direction with the feature-space direction from the previously selected event to each candidate:
The first step, or a step with effectively zero trajectory velocity, uses position distance only.
Tabu and temperature
The most recently selected Tabu_length events are excluded when possible. Python further clamps the effective tabu length to at most n_events − 1, so the candidate pool cannot be permanently emptied.
At temperature below approximately 1e-6, selection is greedy. Otherwise the candidate set is limited to:
Selection among those K nearest candidates uses inverse-distance probabilities, evaluated in log space for numerical stability:
This is not the usual exp(−distance / T) Boltzmann softmax.
Distance-weight presets
| Preset | Canonical weights: centroid, flatness, entropy, flux, rms | Notes |
|---|---|---|
| Uniform | 1.0, 1.0, 1.0, 1.0, 1.0 | Equal weighting among the dimensions active in the selected state space. |
| Brightness focus | 2.0, 0.8, 0.8, 0.8, 0.6 | Places the strongest position-distance emphasis on centroid. |
| Noisiness focus | 0.6, 2.0, 1.5, 0.8, 0.6 | Emphasizes flatness and, when present, entropy. |
| Energy focus | 0.6, 0.6, 0.8, 1.0, 2.5 | 5D only. Praat stops with an error if this preset is selected below 5D because RMS is not part of those spaces. |
| Transient focus | 0.8, 0.8, 0.8, 2.5, 1.0 | Emphasizes flux whenever flux is active. |
Audio reconstruction
The Python stage writes a plan containing original Praat event IDs. This matters because Python can filter event rows by minimum duration; retaining the original IDs prevents an index shift between the Python event list and Praat's source-event arrays.
For every plan step, Praat extracts the corresponding interval from the original Sound, not from the representative analysis channel. Consequently mono remains mono and multichannel input remains multichannel.
True overlap crossfade
Crossfade_ms is implemented with Praat's Concatenate with overlap. The effective overlap is:
The clamp prevents a crossfade from consuming an excessive fraction of a short selected event. Output duration is therefore the sum of selected event durations minus the overlaps between adjacent events.
Parameters and controls
| Parameter | Default | Current behaviour |
|---|---|---|
| Attractor_type | Lorenz | LimitCycle (Hopf), Lorenz, Rossler, LogisticMap. |
| State_dims | 3D | 2D, 3D, 4D, or 5D feature spaces defined above. |
| Weight_preset | Uniform | Uniform, Brightness focus, Noisiness focus, Energy focus (5D only), Transient focus. |
| Num_events_output | 300 | Clamped to 10–2000. |
| Tabu_length | 12 | Praat clamps to 0–500; Python further clamps to at most source-event count minus one. 0 disables memory. |
| Temperature | 0.15 | Clamped to 0–1; controls candidate-pool size and inverse-distance stochasticity. |
| Seed | 1234 | Controls stochastic selection and the LogisticMap initial condition. Identical input/settings/seed reproduce the Python plan. |
| Velocity_weight | 0.0 | Clamped to 0–1; blends position distance with direction mismatch. |
| Coupling | 0.0 | Clamped to 0–1; pulls the next target toward the previously chosen event. |
| Crossfade_ms | 10 ms | Praat clamps to 0–200 ms; actual overlap is also limited by the shortest selected event. |
| Min_event_duration_ms | 30 ms | Events shorter than this are not retained for the plan. |
| Silence_threshold_dB | −25 dB | Threshold passed into Praat's silence segmentation after Intensity analysis of the representative channel. |
| Min_silent_interval | 0.05 s | Minimum silent interval used by To TextGrid (silences). |
| Min_sounding_interval | 0.03 s | Minimum sounding interval used by silence segmentation; Min_event_duration_ms is then enforced separately. |
| Draw_visualization | on | Draws the current process visualization. |
| Play_result | on | Plays the final Sound after processing. |
| Debug | off | Requests the Python debug CSV containing actual mapping target and distance diagnostics. |
Visualization
The current figure is organized around the transformation process rather than parallel spectrogram comparison. The two spectrogram panels used in older versions are no longer part of the visualization.
1. Original waveform
Shows the representative strongest-RMS analysis channel. Red vertical lines mark the starts of the valid source events retained by Praat. The x-axis is source time.
2. Output waveform
Shows the rendered montage. For multichannel output the displayed waveform uses the analysis-channel number when that channel exists in the output, otherwise channel 1. It is a display choice only; the Sound itself keeps its original channel count.
3. Plan timeline
This panel places source event index on the vertical axis and output time on the horizontal axis. One horizontal bar is drawn for each plan step using the event's real duration and the effective crossfade timing. Repeated rows expose event reuse directly; movement across rows shows how the plan traverses the source-event pool.
The bar colours are shared with the clusters in the phase-space panel below. The clustering is a visual reading aid only; it does not participate in the event-selection algorithm.
4. Trajectory-to-event mapping
This is the dominant panel. Python chooses the two most strongly weighted active dimensions for the 2D projection. Under Uniform weighting, the first two active dimensions are used. The axes therefore change with state dimensionality and weighting preset; they are not always centroid and flatness.
- Hollow coloured circles: analyzed source events, sampled evenly to at most 200 points for display.
- Dark line with arrows: the dynamical trajectory, sampled evenly to at most 800 points.
- Purple filled dots: selected events in plan order, sampled to at most 200 points.
- Badges 1–5: the first five displayed plan positions, showing where the selection path begins.
The projected event cloud is deterministically grouped into one, two, or three k-means clusters depending on displayed cloud size. Those cluster colours are reused in the timeline. This k-means layer exists only in the Praat visualization and does not modify the Python mapping or the audio.
5. Summary
The final strip reports attractor/state dimensionality, source and output event counts, unique-event usage, repetition rate, trajectory speed, mapping controls, mean position mapping distance, original/output RMS, durations, analysis channel, effective crossfade, seed, and a short attractor description.
Output and edge cases
- Naming:
<source>_phaseSpace_<attractor>. - Channels: output preserves the channel count of the selected source Sound.
- Level: the script does not apply a final peak normalization or limiter. RMS is measured and reported, not forced to match the input.
- Minimum source material: at least three valid segmented events are required. Otherwise the script stops and asks for segmentation/minimum-duration adjustments.
- Plan length: Python clamps requested output events to 10–2000; Praat accepts the valid rows returned by
plan.csv. - Temporary files: WAV, event CSV, plan, stats, and dependency-probe files are cleaned up at the end or on the handled failure paths.
- Source-event identity: plan indices refer back to original Praat event IDs, so Python-side filtering does not silently remap an event to the wrong source interval.