LZ-Inspired Audio Variations — User Guide

Lempel–Ziv-based audio variation: overlapping audio windows are quantized into a symbol sequence, parsed into a variable-length LZ78 phrase dictionary, and recombined as contiguous source runs for transformation and resynthesis.

Author: Shai Cohen Affiliation: Department of Music, Bar-Ilan University, Israel Version: 0.8 (2026) License: MIT License Repo: https://github.com/ShaiCohen-ops/Praat-plugin_AudioTools
Contents:

What this does

This script converts a continuous audio recording into a symbolic sequence and applies LZ78 parsing to derive a dictionary of variable-length phrases. Each phrase maps back to a contiguous region of the source, allowing the symbolic structure to guide audio recombination.

The process is:

  1. Convert the selected Sound to mono when necessary.
  2. Segment it into overlapping windows on a whole-sample grid.
  3. Extract two acoustic features from every window.
  4. Min-max normalize those two features and vector-quantize them by k-means into discrete symbols.
  5. Parse the symbol string left-to-right with LZ78.
  6. Generate a new sequence of contiguous source runs using one of three LZ-derived generation modes.
  7. Apply one optional variation process to each generated run.
  8. Join run boundaries using the analysis overlap, then trim or pad to the exact requested duration.

Key Features:

Dictionary structure: the LZ78 dictionary is represented as a trie. Each node stores a parent, final symbol, phrase depth, traversal count, and the first source-window position at which that phrase occurred. Node depth is the phrase length in windows.

Quick start

  1. Select exactly one Sound object.
  2. Run LZ-Inspired_Audio_Variations.praat.
  3. Choose a preset or Custom.
  4. Choose the acoustic analysis: Pitch, Spectrum, or Intensity.
  5. Set Window_size_s and Overlap.
  6. Choose an Alphabet_size.
  7. Choose one generation mode:
    • Parse and substitute (LZ78 re-decode)
    • Incremental parsing (LZ78 continuation)
    • LZ77 copy and deviate
  8. Set Novelty, Max_copy_windows, one Variation_method, Variation_amount, Output_duration_s, and optional Random_seed.
  9. Click OK. Output is named <source>_LZ_<preset>.
Recommended first test: use Faithful Re-decode, set Output_duration_s to the source duration, and listen with Novelty = 0 / Variation = None. This exercises the LZ78 encoding and re-decode path with no phrase substitution.
Faithful Re-decode is not a bit-identical bypass. The preset does not automatically replace Output_duration_s with the source duration. The renderer also applies final 5 ms edge fades and peak-scales to 0.95. In addition, only complete analysis windows belong to the parsed stream, so an arbitrary source may have a short uncovered tail after the final full window.

Symbolization

Whole-Sample Analysis Grid

requested window samples = round(Window_size_s * sample_rate) requested hop samples = round(Window_size_s * (1 - Overlap) * sample_rate) effective: window_size_s = win_samples / sample_rate hop_size = hop_samples / sample_rate overlap = 1 - hop_samples / win_samples num_windows = floor((duration - window_size_s) / hop_size) + 1

The script requires at least four complete analysis windows. Window and hop are quantized to integer samples so repeated extraction boundaries do not alternate between fractional-sample phases.

Two Features per Window

AnalysisFeature 1Feature 2
PitchMean F0 (75–600 Hz Pitch analysis)F0 standard deviation
SpectrumSpectral centre of gravitySpectral standard deviation
IntensityMean intensity using Praat “energy” averagingMaximum intensity

Feature 1 determines whether a window is considered defined. In Pitch mode, an unvoiced window normally has undefined mean F0 and therefore receives the dedicated undefined symbol described below. If feature 1 is defined but feature 2 is undefined, the normalized second coordinate is set to 0.5.

2D Min-Max Normalization

For defined windows only: x = (feature1 - min(feature1)) / (max(feature1) - min(feature1)) y = (feature2 - min(feature2)) / (max(feature2) - min(feature2)) If feature2 is undefined while feature1 is defined: y = 0.5

Vector Quantization

Requested Alphabet_size: clamped to 2...64 codewords Actual k-means codewords: min(requested alphabet, number of defined windows) Initialization: k-means++ Iteration: Lloyd assignment/update maximum 40 iterations Empty cluster: reseed at the point currently worst served by its assigned centroid After convergence: renumber clusters by ascending feature-1 centroid

Renumbering gives the symbol labels an interpretable order along the primary feature. If undefined windows exist, they receive one additional symbol after the k-means codewords. Consequently, the actual alphabet can contain one more symbol than the requested Alphabet_size.

Mostly undefined material: if all but at most two windows lack a defined primary feature, the script aborts and recommends Spectrum or Intensity analysis instead.

Symbol Entropy

H = -sum_s p(s) * log2(p(s)) maximum for the actual alphabet: log2(A) bits

This describes the marginal symbol distribution only; it does not measure sequential phrase structure. LZ complexity addresses that separately.

LZ78 Parse & Measures

Incremental LZ78 Parse

Start at the root and read the symbol stream left to right. At each phrase: follow existing trie edges for as long as possible when the next symbol has no child: create a new node = longest known prefix + that new symbol Stored for each node: parent final symbol depth traversal count first source-window occurrence phrase length = node depth

Because every phrase corresponds to a consecutive span in the symbol stream, its first occurrence maps directly to one contiguous stretch of source audio. If the stream ends while traversing an already existing phrase, the remaining suffix is emitted as the final token without adding another node.

LZ Measures Reported

MeasureCurrent implementation
LZ complexity c(n)Number of parsed LZ78 phrase tokens
Normalized complexityc(n) × log_A(n) / n
Mean phrase lengthMean number of source windows per parsed token
Longest phraseMaximum phrase length in windows
Symbol entropyShannon entropy of the quantized symbol stream
Coded-size estimatec(n) × [log2(max(2,c(n))) + log2(A)] compared with n × log2(A)
Interpretation: the normalized-complexity “random → 1, repetitive → 0” statement is an asymptotic heuristic for this LZ normalization, not a guaranteed finite-file calibration. Likewise, the coded-size figure is a rough estimate, not a real entropy-coded file size or measured compression ratio.

Generation Modes

All three modes ultimately emit the same renderable object: a run represented by a source-window start and a length in windows. A run is always a contiguous stretch of the source and therefore has no internal splice boundaries.

1. Parse and Substitute — LZ78 Re-decode

Replay the source LZ78 token sequence in order. For each phrase: with probability Novelty, IF another dictionary node of the same depth exists: substitute another same-length phrase Otherwise: keep the original parsed phrase If the requested output is longer: cycle through the parse again.

Substitution preserves phrase depth, so it preserves the number of windows advanced by that token. Actual substitution rate can be lower than Novelty when no alternative phrase of the same depth exists or no different node is found within the retry limit.

Novelty = 0: this replays the source's parsed phrase sequence. It is the closest thing to a decoder correctness test, but exact source comparison additionally requires a matching Output_duration_s and allowance for final fades, peak scaling, and any uncovered source tail.

2. Incremental Parsing — Variable-Order LZ78 Continuation

Maximum context searched = 8 output symbols. For each next symbol: 1. Begin with up to the last 8 generated symbols. 2. Re-descend the LZ78 trie from the root. 3. If that context has no continuation: drop its oldest symbol and try again. 4. With probability Novelty: force context depth = 0. 5. Sample a child in proportion to its trie traversal count. 6. Realize that symbol as a source window: prefer the window immediately following the previous source run when its symbol matches; otherwise choose a random occurrence of that symbol. 7. Merge consecutive source windows into one contiguous run, up to Max_copy_windows.

The mean context order actually used is reported. This is what makes the mode variable-order rather than a simple carried-node walk that repeatedly dies at trie leaves.

Visualization nuance: amber tokens mark an explicit Novelty escape or a symbol-selection fallback. Ordinary backoff from a long context to a shorter valid context is part of the predictor and is not necessarily colored as an escape.

3. LZ77 Copy and Deviate

Context search: compare the recent generated-output suffix with occurrences inside the source symbol stream maximum matched context: 8 symbols if several source contexts tie: choose one at random then: copy forward from just after that matched context for a random run length 1...Max_copy_windows with probability Novelty: append one literal source window intended to differ from the next copied-source symbol

This is best understood as an LZ77-style generative context matcher, not as a second literal compression pass. The code searches a source continuation predicted by a matching recent output context; it does not write an encoded bitstream or persist explicit offset/length/literal triples as a compressed file.

Novelty by Mode

ModeNovelty means
Parse and substituteProbability of attempting to replace the current phrase by another dictionary phrase of the same depth
Incremental parsingProbability of forcing the predictor to root context before choosing the next symbol
LZ77 copy and deviateProbability of appending a literal after the copied run

Max_copy_windows by Mode

Parameters & Presets

ParameterDefaultBehavior
PresetCustomCustom + five musical presets + Faithful Re-decode test
Analysis_typePitchPitch / Spectrum / Intensity
Window_size_s0.1Minimum 10 ms, then quantized to whole samples
Overlap0.5Clamped 0–0.95, then effective value follows sample-quantized hop
Alphabet_size12Requested k-means codewords, clamped 2–64; actual alphabet may include one extra undefined symbol
Generation_modeIncremental parsingParse/substitute / Incremental parsing / LZ77 copy+deviate
Novelty0.35Clamped 0–1; mode-specific departure probability
Max_copy_windows8Minimum 1; mode-dependent run-length limit
Variation_methodPitch shiftPitch shift / Time stretch / AM / Spectral lowpass / Reverse / Granular shuffle / None
Variation_amount0.5Clamped 0–1
Output_duration_s10Non-positive value becomes 1 s
Random_seed00 = unpredictable; positive = reproducible
Draw_visualizationOnDraw v0.8 parse-centered visualization
Play_resultOnPlay final result

Built-in Presets

PresetAnalysisWindow / overlapAlphabetGenerationNoveltyMax copyVariation
Subtle TexturePitch150 ms / 60%10Incremental parsing.258Pitch shift .3
Rhythmic ShuffleIntensity80 ms / 40%8LZ77 copy+deviate.456Granular shuffle .6
Spectral MorphSpectrum120 ms / 50%14Parse+substitute.508Spectral lowpass .5
Glitch VariationsSpectrum50 ms / 30%16LZ77 copy+deviate.804Reverse .8
Ambient DriftPitch200 ms / 70%8Incremental parsing.1512Time stretch .4
Faithful Re-decodeSpectrum50 ms / 50%20Parse+substitute08None / 0
Preset scope: presets override analysis, window, overlap, alphabet, generation mode, Novelty, Max_copy_windows, variation method, and amount. They do not override Output_duration_s, Random_seed, visualization, or playback.

Variation Methods

Variation is applied to the entire generated run, not separately to every analysis window inside it. Because an LZ phrase can span several windows, this preserves continuity inside the phrase.

1. Pitch Shift

shift_semitones ~ Gaussian(0, 12 * amount) factor = 2^(shift_semitones / 12) Praat Manipulation / PitchTier resynthesis

Segments shorter than 3 / 75 = 40 ms bypass the PSOLA transformation and are counted in the Info report.

2. Time Stretch

stretch_factor = 1 + Gaussian(0, amount) stretch_factor = clamp(stretch_factor, 0.5, 2.0)

The same 40 ms PSOLA guard applies. Time stretch is the only shipped variation that intentionally changes run duration, so the generator creates extra material before the final trim.

3. Amplitude Modulation

mod_freq = 10 * (1 + 10 * amount) output = input * (1 + amount * sin(2*pi*mod_freq*t))

4. Spectral Lowpass

cutoff = 1000 + Uniform(-500,500) * amount frequencies ABOVE cutoff: multiply by (1 - amount) frequencies at/below cutoff: unchanged

The Spectrum-to-Sound result is trimmed back to the original run duration.

5. Reverse

reverse whole run with probability Variation_amount

6. Granular Shuffle

internal grain size = 20 ms if amount ~ 0: identity copy otherwise: maxShift = round(number_of_grains * amount) for each output grain position: choose uniformly from a valid source-index neighborhood +/- maxShift

The trailing partial grain is retained. This is local resampling, not necessarily a permutation: source grains may repeat and others may be omitted.

7. None

The generated contiguous run passes through unchanged. This is useful when the musical effect of the symbolic/LZ process itself should be heard without an additional transformation layer.

Rendering

One Run = One Contiguous Extract

A run starting at window w with length L spans: start = start(window w) end = end(window w + L - 1) audio duration = window_size + (L - 1) * hop

There are no joins between the windows inside a phrase/run. Crossfades occur only between generated run boundaries.

Overlap-Derived Join

nominal crossfade = overlap * window_size For an unmodified run of L windows: runDuration - crossfade = window + (L-1)*hop - overlap*window = L * hop

This aligns phrase-level audio advance with the underlying symbol/window stream. It is the key difference from the old fixed 2 ms phrase join.

Implementation bounds: the current code forces a minimum 2 ms crossfade. Therefore Custom settings with zero or extremely small overlap do not satisfy the exact formula above. In addition, if a transformed segment becomes too short, the final crossfade is capped globally to 90% of the shortest rendered segment. When that cap fires, exact phrase tiling is no longer preserved, although the final duration is still corrected by trim/pad.

Exact Output Duration

Generate enough runs to cover the requested duration. Render runs while accumulated duration is needed. Join all rendered runs using Praat: Concatenate with overlap If result is too long: trim to Output_duration_s If result is too short: append silence to Output_duration_s Then: up to 5 ms fade at beginning and end peak-scale to 0.95

Stereo or multichannel input is converted to mono before feature extraction and rendering, so the output is mono.

Visualization

The visualization exposes the symbolic parse, phrase structure, generated source mapping, and rendered result.

A. SYMBOL STREAM AND LZ78 PARSE x = source-window index y = quantized symbol alternating bands + vertical rules = phrase boundaries undefined-feature symbol = neutral grey B. LZ78 GROWTH x = phrase index y = phrase length in windows dashed line = mean phrase length C. DICTIONARY PROFILE histogram of phrase lengths lengths above 40 are folded into the final displayed bin D. GENERATED TOKEN STREAM -> SOURCE WINDOWS x = rendered output token/run y = source-window span vertical bar length = run/phrase depth connector = movement through source material Parse+substitute: amber = substituted phrase Incremental parsing: amber = explicit escape/fallback event LZ77: amber = emitted literal E. RENDERED OUTPUT final mono waveform duration number of rendered segments actual overlap used at joins SUMMARY windows / alphabet / symbols used / entropy effective window + overlap LZ78 phrase count / mean / longest c(n) / normalized complexity / coded-size estimate generation mode / Novelty / Max_copy_windows generated token count variation / amount final duration / peak / seed
Visualization size: the implementation uses a 9.10-unit Picture page height to fit the five panels plus summary strip, even though the changelog still refers to the earlier “8-inch page convention”.

Applications

LZ Phrase Recomposition

Parse and substitute preserves the source's phrase-length grammar while replacing some phrases with other dictionary entries of the same depth.

Variable-Order Machine Improvisation

Incremental parsing uses recent generated symbols as context, backs off to shorter contexts as necessary, and samples continuations from LZ78 trie traversal statistics.

Context-Matched Copying and Deviation

LZ77 copy and deviate finds source locations whose preceding symbol context resembles the recent generated output, then copies forward and optionally injects literal deviations.

Structure / Complexity Analysis

The visualization and Info report can be used to compare symbol entropy, phrase counts, phrase growth, phrase-length distributions, and the normalized LZ complexity of different sounds or parameterizations. These values depend on the chosen analysis, window/hop, and alphabet size; they are not source-invariant perceptual complexity scores.

Hear the Parse Without Effects

Select Variation = None to separate the symbolic recomposition mechanism from pitch, time, filtering, reversal, or granular processing.

Scope: this is an offline composition and resynthesis tool based on LZ78 phrase parsing and LZ-derived generation strategies. It is not a general-purpose lossless audio compressor or an audio-restoration system. The LZ77 mode is a generative context-and-copy mechanism rather than an exported compressed-code implementation.

Troubleshooting

Almost all Pitch windows are undefined:
Use Spectrum or Intensity analysis. Undefined Pitch windows do receive a valid dedicated symbol, but the script aborts when too few defined windows remain to establish a meaningful 2D codebook.
Very little phrase growth:
A very large alphabet can suppress recurrence. Reduce Alphabet_size, increase overlap, or use a coarser analysis/window configuration.
Faithful Re-decode is not the same length as the source:
Set Output_duration_s manually to the source duration. The preset intentionally changes the LZ/variation controls but does not override output duration.
Pitch shift / Time stretch skipped on some tokens:
Runs shorter than 40 ms bypass the PSOLA stage to avoid Praat's minimum-duration failure.
Output timing changes after Time stretch:
Expected. Time stretch changes individual run duration. The script over-generates material, may cap the join overlap if a transformed segment becomes too short, and finally trims/pads to the exact requested duration.