Rhythmic Fractal Granulator — User Guide

Generates a mirrored, recursively subdivided event-time pattern, extracts source grains by random or sequential reading, applies generation-dependent amplitude envelopes, and overlap-adds the grains at their target times into a fixed-duration output buffer.

Author: Shai Cohen Affiliation: Department of Music, Bar-Ilan University, Israel Version: 0.5.1 (2026) Concept: Recursive rhythmic subdivision + granular overlap-add License: MIT License Repo: GitHub
Contents:

What this does

Rhythmic Fractal Granulator separates two ideas that should not be conflated:

  1. When grains occur is determined by a recursively subdivided timing pattern with bounded jitter and exact left/right mirroring.
  2. What each grain contains is determined independently by Random Offset or Sequential Scan through the selected source.
selected Sound → zero-based internal source copy when needed → recursive left-half event generation → exact mirrored event times → chronological sort → source-grain extraction → generation amplitude envelope → true overlap-add into fixed output buffer → optional mono→stereo duplication → optional target peak normalization
The synthesis stage uses a pre-allocated Sound and adds each grain at its target sample range. Grains that overlap in time are summed at the intended positions; they are not pushed later by concatenation.

What “fractal” means here

The script uses a recursive dyadic-subdivision-inspired timing rule. Each generation introduces a smaller temporal shift, approximately half the previous generation's scale. This creates a hierarchy of event spacings.

This is not a calculation of fractal dimension, an iterated-function-system audio model, or exact mathematical self-similarity at every scale. Random timing jitter perturbs the ideal dyadic positions. “Fractal” here describes the recursive multi-scale timing construction.

After the left-half pattern is generated, every event is copied to the corresponding mirrored time on the right:

t_right = Total_duration - t_left

The mirrored half therefore preserves the exact jittered timing relationships of the generated left half under temporal reflection.

Quick start

  1. Select exactly one Sound object.
  2. Run Rhythmic_Fractal_Granulator.praat.
  3. Choose Custom or one of the five named presets.
  4. Set output duration, grain duration and number of generations.
  5. Choose Bell (Hanning) or Plateau (Trapezoid).
  6. Choose Random Offset or Sequential Scan.
  7. Choose whether a mono source should be duplicated to stereo and whether the final output should be peak-normalized.
  8. Run. The output is automatically played at the end.
The form has no Play_result switch in v0.5.1. Playback is unconditional after processing.

Event-time generation

Output geometry

pivot = Total_duration / 2 centerBuffer = min(0.05 s, Total_duration × 0.10) halfDuration = pivot - centerBuffer seedOffset = min(0.10 s, halfDuration × 0.25)

The historical 50 ms center reserve and 100 ms seed offset are therefore retained for normal output durations, but both scale down safely for very short custom renders.

Generation rule

The left half begins with one Generation-0 seed event at seedOffset. For generation g = 1...Generations:

shift(g) = halfDuration / 2^g generationJitter = min(0.005 s, 0.45 × shift(g))

The script snapshots the number of events that already exist at the beginning of that generation. Every one of those existing events is then eligible to spawn one new child:

childTime = parentTime + shift(g) + Uniform(-generationJitter, +generationJitter)

The child is retained only if it remains inside the legal left-half region.

Jitter is capped at 45% of the local subdivision shift. At deep generations, where the nominal shift becomes smaller than 5 ms, the jitter automatically contracts with it instead of remaining fixed.

Event count

If every eligible parent successfully produces a child, the left-half population doubles at each generation:

maximum left events ≈ 2^Generations maximum total after mirror ≈ 2^(Generations + 1)

Actual counts can be lower because children that fall beyond halfDuration are rejected. Generations are limited to 0–11 and the arrays are additionally guarded at 5000 events.

Mirroring and sorting

After the left half is complete, every event is mirrored around the output midpoint with its generation number unchanged. The complete event list is then sorted chronologically before source reading and synthesis.

Event-time randomness exists even in Sequential Scan mode because the recursive timing generator itself uses jitter. There is no random-seed control.

Source reading

The source is never time-stretched or pitch-shifted. Each event simply extracts one rectangular grain of the effective grain duration from some location in the source.

Effective grain duration

The requested grain duration is internally clamped so that it:

This protects both the legal source-read range and the Hanning denominator N-1.

Random Offset

readStart ~ Uniform( 0, sourceDuration - effectiveGrainDuration )

A new random source position is drawn independently for every output event.

Sequential Scan

After the fractal events have been sorted by output time, source positions progress linearly through the entire legal read-start range:

scanStep = (sourceDuration - effectiveGrainDuration) / (numberOfEvents - 1)

The first event reads from source time 0; when there is more than one event, the last event reaches exactly sourceDuration - effectiveGrainDuration.

Sequential Scan is therefore chronological with respect to the sorted output events. It does not follow generation order or tree genealogy.

Non-zero source time domains

All synthesis reads use 0…sourceDuration coordinates. If the selected Sound does not start at time 0, the script creates a temporary zero-based processing copy. The user's original Sound is unchanged.

Grain amplitude & envelopes

Generation amplitude

Amplitude decreases by a fixed 15% multiplier per generation:

amplitude(g) = 0.85^g

This value is hard-coded in v0.5.1; it is not a public parameter.

Bell (Hanning)

The sample-domain bell is:

w[n] = sin( π × n/(N-1) )^2

The grain is multiplied by 0.85^g × w[n]. The first and final samples are zero.

Plateau (Trapezoid)

The current implementation is a genuine linear trapezoid:

first 20%: linear 0 → 1 middle 60%: 1 last 20%: linear 1 → 0

The same generation amplitude multiplier is applied throughout the envelope.

Earlier versions used Praat Fade commands, which are raised-cosine ramps. v0.5 implements the “Trapezoid” label literally with linear attack and release.

True overlap-add

A silent output Sound of exactly Total_duration is created before any grain is rendered. It has the source sample rate and the source channel count.

For event time t:

targetStartSample = round(t × sampleRate) + 1

The windowed grain samples are then added directly into the corresponding output sample range:

output[channel, n] += grain[channel, n - offset]

If multiple grains overlap, their samples add. There is no voice stealing, overlap limit, automatic gain compensation, or per-overlap normalization.

Output-edge clipping

If a grain begins near the end of the fixed output buffer, only the portion that fits before Total_duration is added. The event is not shifted earlier and the output is not extended to accommodate the grain tail.

Five named presets + Custom

Named presets overwrite only Total_duration, Grain_duration, Generations, Window_shape and Read_mode. They do not overwrite Stereo, Normalize or Draw_visualization.

PresetTotalGrainGenerationsWindowRead mode
Dense Texture6.0 s50 ms6BellRandom
Sparse Rhythmic8.0 s200 ms3TrapezoidSequential
Glitchy3.0 s20 ms7BellRandom
Ambient Cloud10.0 s150 ms4BellRandom
Percussive4.0 s80 ms3TrapezoidSequential

Parameters & effective limits

ParameterDefaultExact behavior
PresetCustomCustom plus five named configurations.
Total_duration_(s)4.0Must be >0. Defines the output-buffer duration and timing scale.
Grain_duration_(s)0.1Must be >0; internally clamped to source duration and at least two source samples when possible.
Generations5Integer 0–11; controls recursive subdivision depth.
Window_shapePlateauBell/Hanning or linear trapezoid.
Read_modeRandom OffsetIndependent random source positions or full-range sequential scan.
StereoOnIf source is mono, duplicate final mono output to stereo. Does not pan or spatialize.
NormalizeOnTarget Sinc70 peak scaling to 0.95 when non-silent.
Draw_visualizationOnDraw the v0.5.1 fractal-timing visualization.

Source minimum

The source must contain at least two samples. There is no fixed minimum duration such as 0.5 seconds.

Channels & the Stereo option

Grain extraction and overlap-add operate on all source channels independently. The initial output buffer is created with the same channel count as the source.

SourceStereo offStereo on
MonoMono outputMono result duplicated to two identical channels
StereoStereo preservedStereo preserved; no additional conversion
3+ channelsChannel count preservedChannel count preserved; not reduced to stereo
Stereo is not a spatialization control. There is no panning, stereo-width modulation, independent L/R grain position, or decorrelation. For a mono source, enabling Stereo simply duplicates the completed mono Sound into two channels.

Normalization

When Normalize is enabled, the completed result is measured with Praat's Sinc70 absolute extremum. A non-silent result then receives:

Scale peak: 0.95

This is target peak normalization, not an attenuate-only ceiling. A quiet output can be amplified to the 0.95 target.

When Normalize is off, overlapping grains are left at their raw additive level. No limiter or clipping-prevention stage follows.

Visualization

The v0.5.1 Picture view preserves the tool's distinctive timing analysis while aligning the waveform geometry and color semantics.

  1. Source waveform — original selected Sound.
  2. Recursive Subdivision — event positions arranged by generation, with the mirror axis marked.
  3. IOI distribution — 24-bin logarithmic histogram of successive event intervals.
  4. Radial clock — angular position = normalized event time; radius = generation.
  5. Output waveform — completed result.
  6. Summary — event count, mirrored count, generations, effective grain duration, window, read mode and output duration.

Shared waveform scale

Source and Output share one amplitude range based on the larger Sinc70 peak, with 8% visual headroom. They can therefore be compared directly in level as well as shape.

Generation color

Color has one semantic meaning: generation. Generation 0 begins warm and later generations move through the palette toward cooler colors. Generations above 7 share the final color and are labeled 7+ in the legend.

Recursive Subdivision connectors

The connecting lines are a visual nearest-previous-generation reconstruction, not stored DSP genealogy. Actual parent indices were created during left-half generation but become stale after chronological sorting; the visualization therefore connects each event to the nearest event in generation g−1.

IOI log-histogram

Bars show the measured intervals between chronologically adjacent events. The dotted reference lines show the ideal dyadic scales:

halfDuration / 2^k

Jitter, mirroring and the interaction among multiple generations can broaden or redistribute the actual histogram relative to those reference positions.

Radial clock

The inner viewport is explicitly square, so the clock is physically circular. Event angle follows time across Total_duration; radial distance follows generation. The vertical diameter marks the mirror axis.

Output behavior

Output duration is independent of source duration, but available grain content is not: the effective grain duration can never exceed the source, and Sequential Scan traverses only the legal source-read range.