Granular Navigation Engine — User Guide

A corpus-based granular navigation tool that analyzes a folder of audio, learns a compact acoustic embedding, generates a path through selected grains, and reconstructs that path in Praat as mono or latent-positioned stereo.

Author: Shai Cohen Affiliation: Department of Music, Bar-Ilan University, Israel Praat front end: v1.7.1 (2026) Python engine: v1.7.1 License: MIT License Python dependencies: PyTorch + NumPy + soundfile
Contents:

What is this?

Granular Navigation Engine treats a folder of recordings as one searchable corpus of overlapping audio grains. Python measures each grain, learns a 16-dimensional latent representation with an autoencoder, and chooses a sequence of unique grains according to the selected navigation mode. Praat then returns to the source files and rebuilds that sequence from the original audio regions.

The simplest description:
Choose a folder, choose how you want to move through its acoustic space, and the tool constructs a new sound by following that path through grains drawn from the corpus.

The learned representation is used to organize and navigate the corpus. It does not synthesize new spectral content: the final sound is assembled from extracted regions of the source recordings.

Complete pipeline

Input: a folder containing WAV, AIFF/AIF, or FLAC files
Python: scan top-level audio files in filename order and divide each into 50%-overlapped analysis grains
Python: extract 14 acoustic features from every grain
Python: median/IQR normalization → train a compact autoencoder → 16-D latent embedding
Python: calculate a 2-D PCA projection for visualization and latent stereo position
Python: generate a no-repeat grain path using the selected navigation rule
Praat: re-read only the needed source files, extract the selected regions, convert them to a common mono representation, and resample when necessary
Optional stereo: map PCA dimension 1 to equal-power panoramic position, with width and inertia controls
Output: concatenate the selected grains with overlap and normalize the final Sound to a target peak of 0.92

Corpus and grain construction

Files

The Python engine scans the selected folder itself. It reads supported audio files located directly in that folder; it does not recursively scan subfolders. Supported extensions are .wav, .aif, .aiff, and .flac. Files are processed in sorted filename order.

Analysis grains

Each file is divided into grains with a hop equal to 50% of the grain duration. A final partial grain is accepted when at least 20 ms of source audio remains. For feature extraction, a short final grain is zero-padded to the nominal grain size, while its stored end time still marks the real end of the source material.

grain duration = Grain_ms
hop duration = 0.5 × grain duration
The Python engine uses an effective analysis-grain range of 25–500 ms. Values entered outside this range are constrained by Python. For consistent analysis and reconstruction settings, use Grain_ms within 25–500 ms.

Channel used for analysis

Multichannel files are normally analyzed as the arithmetic mean of their channels. Before committing to that mean, the engine compares its whole-file RMS with the strongest individual channel. If the mean falls below 10% of the strongest-channel RMS, the engine uses that strongest channel instead. This phase-safe fallback avoids analyzing a nearly cancelled channel sum.

The chosen policy is stored for each grain so Praat can use the corresponding mono representation again during reconstruction.

Acoustic feature space

Each grain is represented by 14 features:

FeatureImplementation
8 spectral-band energiesEight broad bands with linearly spaced frequency boundaries from 80 Hz to 8 kHz, or to just below Nyquist at lower sample rates; log-energy is stored for each band.
Spectral centroidPower-weighted spectral centre in Hz.
Spectral spreadPower-weighted spread around the centroid.
Spectral flatnessGeometric-to-arithmetic power ratio.
Spectral rolloffFrequency below which 85% of spectral power is accumulated.
Zero-crossing rateFraction of adjacent sample pairs that cross or leave zero/sign.
RMSRoot-mean-square amplitude of the analysis grain.

A Hann window is applied for FFT-based spectral measurements. Before learning, each feature dimension is normalized by its corpus median and interquartile range:

normalized feature = (feature - median) / max(IQR, 10⁻⁶)

Learned embedding

The normalized 14-D feature vectors train a compact feed-forward autoencoder. Its encoder maps each grain through 64 and 32 hidden units to a 16-dimensional latent vector. The decoder learns to reconstruct the original normalized feature vector from that latent representation.

The Praat wrapper uses a fixed 80 training epochs and seed 42. The seed stabilizes the intended stochastic choices for repeat runs in the same software environment, while exact numerical identity across different hardware or library builds should not be assumed.

PCA projection

After the latent embedding is ready, Python calculates a two-dimensional PCA projection. These two PCA coordinates are written to the path CSV. They serve two roles:

The PCA projection is a display and spatial-rendering coordinate system. Navigation itself uses either the full 16-D latent embedding or the 14-D normalized feature space, depending on the selected mode.

Navigation modes

The path contains each chosen grain at most once. Already visited grains are removed from the candidate set, so the requested path cannot exceed the number of available grains.

ModeHow the next grain is chosen
SimilarityChooses the unvisited grain nearest to the current grain in the full 16-D learned latent embedding.
SmoothChooses the unvisited grain nearest to the current grain in the normalized 14-D acoustic feature space.
ContrastChooses the grain farthest in latent space from the mean of the recent path history.
BrighterBiases the path toward increasing spectral-centroid character while retaining transition continuity.
DarkerBiases the path toward decreasing spectral-centroid character while retaining transition continuity.
NoisierBiases the path toward higher zero-crossing-rate character.
HarmonicBiases the path toward lower spectral flatness.
DenserBiases the path toward higher RMS amplitude.
SparserBiases the path toward lower RMS amplitude.
Similarity and Smooth are different. Similarity follows distances in the learned latent representation; Smooth follows distances in the original normalized acoustic-feature representation.
Denser and Sparser refer to RMS in this implementation. They describe higher- versus lower-energy grains, not the density of musical events, notes, or attacks.

Starting point

Similarity, Smooth, and all six directional modes begin at the grain closest to the centre of the 16-D latent embedding. Contrast begins from a seeded random grain.

How directional navigation works

For Brighter, Darker, Noisier, Harmonic, Denser, and Sparser, the engine learns a direction inside latent space that predicts the chosen acoustic feature. Candidate grains then receive two components:

combined candidate score = 0.60 × directional projection + 0.40 × latent transition similarity

The transition component is:

transition similarity = 1 / (1 + latent Euclidean distance)

This makes the directional modes biased trajectories rather than strict monotonic ramps. A Brighter path, for example, favors the latent direction associated with higher centroid while still rewarding a coherent transition from the current grain.

Latent stereo rendering

When Stereo output is enabled, Praat constructs stereo from the mono reconstruction grain by grain. The original stereo or multichannel image of the source recordings is therefore not preserved.

PCA X → panorama

PCA dimension 1 (emb_x) provides the target panoramic position. The normalization uses the larger absolute X extent of the entire corpus PCA projection, not just the selected path. PCA zero therefore stays at acoustic centre.

pan target = clamp(PCA-X / whole-corpus |X|max, -1, +1) × Spatial_width

If no usable PCA-X range is available, the stereo result remains centred.

Spatial inertia

The first grain begins exactly at its latent target. Each later grain smooths the target using the previous panoramic position:

pan[n] = inertia × pan[n-1] + (1 - inertia) × target[n]

An inertia of 0 follows every latent point directly. Larger values retain more of the previous position and therefore slow panoramic movement. Custom inertia is constrained to 0–0.98.

Equal-power panning

The smoothed position is converted to an angle from 0 to π/2:

angle = (pan + 1) × π / 4
Left gain = cos(angle)
Right gain = sin(angle)

This is an equal-power left/right mapping. The spatial layer is applied only after Python has already generated the navigation path; changing the spatial preset does not alter feature analysis, embedding, or grain selection.

Spatial presets

Spatial presets set Width and Inertia together. The manual Width/Inertia fields are used only when Custom is selected.

PresetWidthInertiaPan character
Latent Walk0.850.35Broad latent movement with moderate smoothing.
Subtle Drift0.450.60Narrower panorama with stronger continuity.
Wide Flow1.000.55Full width with a flowing, smoothed trajectory.
Active Roam1.000.20Full width with faster response to latent changes.
Maximum Motion1.000.00Direct full-width mapping with no inertial smoothing.
Slow Panorama1.000.85Full possible width but strongly smoothed movement.
Custom0–10–0.98Uses the values entered in the manual fields.
Maximum Motion gives the largest immediate response to the PCA-X trajectory: width 1.00 and inertia 0.00.

Praat reconstruction

Python writes the selected source filename and original start/end time for each path grain. Praat then reconstructs the result in path order using a bounded-memory loop: it opens the current source, extracts the needed grain, appends it to the growing result, and releases temporary objects before continuing.

Mono source used for reconstruction

Thus all navigation reconstruction is based on one mono stream per grain. Stereo, when requested, is created afterward from latent position.

Mixed sample rates

The first successfully reconstructed grain establishes the output sampling frequency. Any later grain from a different sampling rate is resampled by Praat to that frequency before extraction/concatenation continues.

Extracted duration

Praat clamps every path interval to the real source duration and accepts it when at least 5 ms remains. The region is extracted with a rectangular window. The final partial analysis grain is therefore reconstructed only for its real source duration rather than with the zero padding used during feature analysis.

Crossfade

Each new grain is appended with Praat Concatenate with overlap. The requested overlap is:

crossfade = min(12 ms, 0.40 × Grain_ms)

This overlap is part of the output construction: successive source grains overlap in time instead of being butt-joined.

Final level

After assembly, Praat applies Scale peak: 0.92 to the completed Sound. This is target peak normalization, not an attenuate-only ceiling.

Controls

ControlMeaning
FolderCorpus folder. Leave blank to choose the folder with a Praat dialog.
Navigation modeSelects the path-generation rule described above.
Grain_msNominal analysis grain duration. The Python engine constrains effective analysis size to 25–500 ms; use this range for consistent settings.
Path_lengthRequested number of unique path grains. Python constrains the request to 4–1000 and then to the number of grains actually available.
Stereo_outputOn: create latent-positioned stereo. Off: leave the reconstructed result mono.
Spatial_presetChooses a paired Width/Inertia setting.
Spatial_widthCustom maximum excursion from centre, constrained to 0–1. Used only with Custom.
Spatial_inertiaCustom one-pole smoothing amount, constrained to 0–0.98. Used only with Custom.
Draw_visualizationDraws the path analysis figure in the Praat Picture window.
Play_resultPlays the final Sound when processing is complete.
Training epochs and random seed are fixed by the Praat wrapper at 80 epochs and seed 42; they are not form controls.

Persistent analysis and embedding cache

The Python engine uses a two-level persistent cache by default under:

~/.praat_audiotools/gne_cache

Analysis cache

Decoded feature analysis is reused when the corpus signature and analysis configuration are unchanged. The cache key includes the folder, top-level audio filenames, file sizes and modification timestamps, grain/hop duration, feature schema, and relevant NumPy/soundfile versions.

Embedding cache

The trained 16-D embedding and PCA projection are cached separately. Its key additionally includes training epochs, seed, latent dimension, and relevant PyTorch/NumPy versions.

Changing only Navigation mode or Path length can therefore reuse both feature analysis and the learned embedding. The engine still generates a new path for the requested mode/length.

An unreadable cache entry is ignored and recomputed. Failure to create the cache directory disables caching rather than stopping the analysis.

Visualization

When enabled, the Praat Picture window summarizes the selected path rather than drawing the entire corpus.

1. Grain timeline

Each path step is a colored block representing its source file. Up to eight source identities are assigned distinct palette entries for the display.

2. Transition-score curve

The curve shows the latent transition similarity written for each selected edge. Higher values mean smaller 16-D latent distance between consecutive grains:

T-score = 1 / (1 + latent distance)

The final row has no following transition, so its stored transition score is 0.

3. Embedding path

The selected grains are plotted in the 2-D PCA projection and connected in navigation order. Source-file colors match the timeline. The first selected point is marked in green and the last in red.

The plotted limits are based on the selected path. Stereo panning, however, uses the whole-corpus PCA-X reference range written by Python.

4. Mode strip

A colored strip labels the active navigation mode.

5. Summary panel

The summary reports corpus grains, number of source files, mode, phase-safe fallback count, latent dimension, epochs, path length, Python runtime, grain/hop settings, crossfade, and the active spatial preset or mono mode.

Output, naming, and temporary files

Sound name

For mono output:

GNE_<mode>_<assembled-grains>gr

For stereo output:

GNE_<mode>_<assembled-grains>gr_stereo

The number in the name is the number of grains successfully assembled by Praat, which can be lower than the requested path length if a source becomes unreadable or a path interval cannot be reconstructed.

Temporary data

The Python path CSV, statistics file, and redirected Python log are temporary and are deleted during normal cleanup. The reconstructed Sound remains in Praat. The persistent Python cache remains available for later runs.

Crash marker

The wrapper writes a persistent diagnostic stage marker to GNE_last_stage.txt in the Praat preferences directory. It records the most recent major lifecycle boundary and is intended to help localize a hard Praat failure.

Interpretive notes

Recommended starting point

  1. Place a small, coherent group of source recordings in one folder.
  2. Start with Grain_ms = 150 and Path_length = 60.
  3. Use Similarity to hear movement through the learned latent space, then compare it with Smooth to hear nearest-neighbor movement in the measured acoustic features.
  4. For stereo, begin with Latent Walk. Use Maximum Motion when you want the most immediate full-width PCA-X mapping.
  5. Inspect the transition curve and PCA path together with the audio: they show how the selected path moves through the learned corpus representation.