Sound Atom Composer — User Guide

Builds a descriptor-indexed corpus of pitched source moments, selects and reorders those atoms, then resynthesizes a new stereo texture by reading the actual source audio at variable speed with per-atom pan and right-channel timing jitter.

Author: Shai Cohen Affiliation: Department of Music, Bar-Ilan University, Israel Version: 1.3.6 (2026) License: MIT License Repo: GitHub
Contents:

What this does

Sound Atom Composer analyzes a folder of WAV files, identifies voiced moments that satisfy pitch and intensity criteria, stores those moments as a descriptor table, and then uses the selected table rows to read short pieces of the actual source waveform from a concatenated mono SourceBank.

WAV folder → mono + sample-rate unification → ordered SourceBank → Pitch + Intensity analysis → atom dictionary → optional row randomization → Max_atoms selection → variable-rate source reads → Gaussian-windowed stereo overlap-add → 0.99 safety ceiling
Each rendered atom is a short waveform excerpt read from the source corpus. Pitch transposition uses variable-rate source reading, so pitch and atom duration change together.

What is an atom here?

An atom is a qualifying analysis frame plus a fixed requested read duration. The dictionary contains four numeric descriptors:

ColumnMeaningUsed in synthesis?
timeAbsolute time inside the concatenated SourceBank.Yes — source-read start.
durationmax(Time_step × Atom_duration_multiplier, 2/maxSR).Yes — nominal source-grain duration.
frequencyDetected voiced F0 in Hz.Used for selection and visualization, not as an oscillator frequency.
amplitudePraat Intensity value at the analysis time.Used for selection/visualization only; it does not multiply grain gain.
“Dictionary” therefore means a table of references and descriptors into the SourceBank, not a collection of copied grain Sound objects and not a learned embedding.

Quick start

  1. Prepare a folder containing lowercase *.wav files.
  2. Run Sound_atom_composer.praat; no Sound selection is required.
  3. Type a folder path, or leave Folder blank to use Praat's folder chooser.
  4. Choose Custom, Time Stretch, Pitch Shifted Cloud, or Shuffle Texture.
  5. Set analysis thresholds, pitch range, atom limit, transposition, output duration and amplitude scale.
  6. Run. The output object is named Granular_Output.

Corpus preparation

Folder discovery

The script searches the selected folder with the pattern *.wav. If no matching files are found, it exits.

Mono conversion

Every loaded file is converted to mono before corpus assembly. Stereo and multichannel spatial information from the source files is therefore discarded before analysis and synthesis.

Common sample rate

The target sample rate is the highest sample rate among all loaded files. Lower-rate files are resampled to that value with Praat precision 50.

SourceBank order

The files are concatenated explicitly in the order returned by Praat's file list. The script builds fresh accumulator copies so later resampling cannot accidentally reorder the bank through Object-list IDs.

The resulting mono SourceBank is shifted to start at time 0. Per-file start/end positions are retained internally so candidate atoms can be checked against file boundaries.

Analysis & atom dictionary

Pitch analysis

The effective Pitch ceiling is:

analysisPitchCeiling = min(Max_freq, 0.49 × corpusSampleRate)

The lower bound is Min_freq. If the resulting usable range is invalid, the script exits instead of running Pitch outside the corpus Nyquist range.

Intensity analysis

Praat Intensity is calculated with Min_freq as its minimum-pitch parameter and Time_step as the time step. Min_energy is compared directly with Praat Intensity values.

Acceptance test

A frame becomes an atom only when all of the following are true:

F0 is defined AND Intensity(time) > Min_energy AND Min_freq ≤ F0 ≤ Max_freq AND the complete forward atom read stays inside one source file

Unvoiced Pitch frames are skipped; they are not replaced by a fallback frequency.

File-boundary protection

The nominal source read extends forward from the analysis time by:

atomDuration = max(Time_step × Atom_duration_multiplier, 2 / maxSampleRate)

If that interval would cross from one original file into the next within SourceBank, the candidate is rejected. A rendered grain therefore never splices two corpus files inside one atom.

Selection & randomization

After analysis, the table contains all qualifying atoms in SourceBank/time order.

Randomize_order = off

The table remains sequential. If more rows exist than Max_atoms, the first Max_atoms rows are used.

Randomize_order = on

Praat Randomize rows is called before the Max_atoms limit is applied.

When the dictionary contains more atoms than Max_atoms, Randomize_order changes both the selected subset and its order. It is not merely a playback-order shuffle after selection.

Max_atoms is a positive real field for API compatibility, but the engine uses floor(Max_atoms) and enforces an internal minimum of 1.

There is no random-seed control, so row randomization, stereo pan and right-channel jitter can vary between runs.

True granular synthesis

The engine creates two silent mono buffers, Left and Right, each exactly Output_duration long at the SourceBank sample rate.

Destination timing

Selected atoms are distributed uniformly across the requested output timeline:

destinationTime(i) = (i - 1) × Output_duration / numberOfAtoms

The final atom therefore begins one spacing interval before Output_duration rather than exactly at the endpoint.

Actual source audio

For each output sample, the engine reads SourceBank with Praat's object() function:

sourceTime = atom.time + (outputTime - atomStart) × playRate

This is sample-based corpus resynthesis. No sine oscillator is created.

Gaussian-like edge-zeroed window

The nominal Gaussian uses width = renderedDuration / 4. Because a Gaussian at ±2 widths equals exp(-2), the script subtracts that edge value and renormalizes:

raw = exp(-0.5 × ((t-center)/width)^2) window = (raw - exp(-2)) / (1 - exp(-2))

This makes the window reach zero at the rendered interval edges rather than ending at approximately 0.135.

Output-edge clipping

If an atom begins before time 0 because of right-channel jitter, or extends beyond Output_duration, only its intersection with the legal output domain is rendered. The complete atom is not discarded.

Transpose & atom duration

Transposition uses a source-read-rate ratio:

playRate = 2^(Transpose_semitones / 12)

The rendered atom duration is:

effectiveDuration = dictionaryDuration / playRate
This is varispeed, not pitch-preserving time stretching. The preset label “Time Stretch (Slow down, maintain pitch)” should be read as a compositional strategy: the preset keeps transposition at 0 and lengthens the dictionary atom duration, rather than invoking a dedicated time-stretch algorithm.

Stereo field

Source spatial information has already been removed by mono conversion. A new stereo field is created independently for every selected atom.

Random balance

pan ~ Uniform(0,1) gainL = (1 - pan) × Amplitude_scale gainR = pan × Amplitude_scale

This is a linear balance law, not constant-power panning. At pan=0.5, each side receives 0.5 × Amplitude_scale.

Right-channel timing jitter

jitter ~ Normal(mean=0, standard deviation=10 ms)

Left begins at the regular destination time. Right begins at destinationTime + jitter. Jitter can be positive or negative and is safely clipped at output time 0 when necessary.

The stored atomPan value shown in the Stereo Field visualization is this random 0…1 balance parameter. The right-channel timing jitter is not shown as a separate horizontal offset in that panel; both visualization panels use the unjittered destination time on the x-axis.

Three named presets + Custom

The presets overwrite only Time_step, Max_atoms, Transpose_semitones, Randomize_order, Output_duration and Atom_duration_multiplier. They do not overwrite Min_energy, Min_freq, Max_freq, Amplitude_scale, Draw_visualization, Play_result or Folder.

PresetTime stepMax atomsTransposeRandomizeOutputDuration mult.
Time Stretch50 ms10000 stNo20 s2.0
Pitch Shifted Cloud30 ms600+12 stYes8 s1.0
Shuffle Texture100 ms200−5 stYes10 s1.0

Parameters & limits

ParameterDefaultExact role
FolderblankTyped WAV-folder path; blank opens a directory chooser.
Preset_styleCustomCustom plus three named strategies.
Time_step0.05 sPitch/Intensity analysis step and base atom-duration unit.
Min_energy30Minimum Praat Intensity value required for atom acceptance.
Min_freq50 HzPitch-analysis floor and atom-selection lower bound.
Max_freq4000 HzRequested Pitch ceiling/selection upper bound; analysis is capped below Nyquist.
Max_atoms400Floored to an integer ≥1 after optional table randomization.
Transpose_semitones0Varispeed read-rate transposition; no explicit public range clamp.
Randomize_orderOnRandomizes dictionary rows before Max_atoms truncation.
Output_duration10 sExact duration of the L/R synthesis buffers.
Atom_duration_multiplier1.5Dictionary duration = Time_step × multiplier, with a two-sample minimum.
Amplitude_scale1.0Global multiplier applied through per-atom L/R gains.
Draw_visualizationOnDraw SourceBank, Result, Atom Distribution, Stereo Field and Summary.
Play_resultOnPlay Granular_Output after completion.

Min_freq must be smaller than Max_freq. Most numeric fields are positive at the Praat-form level. Amplitude_scale and Transpose_semitones are real fields and are not otherwise clamped by the script.

Amplitude scale & final peak safety

Amplitude_scale directly affects the atom gains. It is not cancelled by unconditional normalization.

After L/R are combined to stereo, the script measures the Sinc70 absolute extremum:

if finalPeak > 0.99: Scale peak to 0.99 else: leave level unchanged

This is an attenuate-only safety ceiling, not target normalization. A quiet result stays quiet; only an over-ceiling result is globally reduced.

Visualization

The v1.3.6 Picture view contains:

  1. Source Bank waveform — concatenated mono corpus.
  2. Result waveform — stereo Granular_Output.
  3. Atom Distribution — selected atom frequency versus destination time; color represents stored Intensity amplitude from quiet to loud.
  4. Stereo Field — random pan parameter versus destination time; color represents atom frequency from low to high.
  5. Summary — atom count, average frequency, transpose, duration, corpus size, analysis range, time step and randomization state.

Shared waveform scale

SourceBank and Result use one shared amplitude scale based on the larger Sinc70 peak plus 5% headroom, so their plotted levels are directly comparable.

Selected frequency display range

The Atom Distribution y-axis zooms to the actually selected frequency range with padding, constrained by Min_freq/Max_freq, rather than always displaying the entire requested analysis range.

The Atom Distribution color uses the dictionary's stored Intensity value, but that value is not a synthesis gain. A visually “louder” atom is not automatically rendered louder than a quieter-colored atom; audio level comes from the source waveform itself, Amplitude_scale, pan balance, overlap, and the final safety ceiling.

Output behavior

The temporary SourceBank and atomDictionary are removed after synthesis; the final Granular_Output Sound remains selected.