Skip to main content

praatfan Usage Guide

This guide covers how to use praatfan from Python and JavaScript/TypeScript (via WASM).

Overview

praatfan provides acoustic analysis functions equivalent to Praat:

Analysis Description
Pitch Fundamental frequency (F0) detection using AC or CC methods
Formant Formant frequencies and bandwidths using Burg's LPC method
Intensity RMS energy contour in dB
Harmonicity Harmonics-to-noise ratio (HNR)
Spectrum Single-frame FFT with spectral moments
Spectrogram Time-frequency representation (STFT)

Installation

Python

# From PyPI (recommended)
pip install praatfan              # Pure Python (works everywhere)
pip install "praatfan[wasm]"      # + the Rust engine as WebAssembly (no compiled wheel needed)
pip install praatfan-rust         # Rust backend (optional, fastest)
pip install "praatfan[sphere]"    # + NIST SPHERE (TIMIT/WSJ/Switchboard) support

NIST SPHERE files (.sph)

Sound.from_file reads SPHERE audio (incl. shorten-compressed) when the optional desphere package is installed; without it, libsndfile still handles uncompressed SPHERE, and other .sph files raise the usual unsupported-format error. The praatfan[sphere] extra pulls desphere[fast] (with a small Rust accelerator for large shorten files); output is byte-identical to the pure-Python decoder. To stay pure-Python, require plain desphere in your own dependencies instead — praatfan uses whichever is installed. The praatfan-rust backend has SPHERE support compiled in, so it needs nothing extra.

# Or build Rust backend from source
cd rust
pip install maturin
maturin develop --features python

JavaScript/TypeScript (WASM)

# Install wasm-pack
cargo install wasm-pack

# Build for web browsers
cd rust
wasm-pack build --target web --features wasm

# Build for Node.js
wasm-pack build --target nodejs --features wasm

The built package will be in rust/pkg/.

Sound.from_wav() accepts WAV and NIST SPHERE bytes (including shorten-compressed): the SPHERE decoder is statically linked into the WASM bundle, so .sph files are handled fully client-side with nothing sent to a server. No extra package or configuration is required.

Rust

Add to your Cargo.toml:

[dependencies]
praatfan = { path = "../praatfan-core-clean/rust" }

R

remotes::install_github("ucpresearch/praatfan-core-clean", subdir = "r-package")
library(praatfan)
# Works immediately: the engine ships as WebAssembly and runs via V8.
# pf_setup() installs the faster native praatfan-open-pipe binary
# (or set PRAATFAN_PIPE to one); once present it is used automatically.

Analysis functions return tidy data.frames:

p <- pf_pitch("audio.wav")                    # time, f0 (NA = unvoiced), strength, voiced
f <- pf_formants("audio.wav")                 # time, n_formants, F1..F5, B1..B5
i <- pf_intensity("audio.wav")                # time, intensity (dB)
h <- pf_harmonicity("audio.wav")              # time, hnr (dB)
m <- pf_spectral_moments("audio.wav", times = c(0.10, 0.15))
b <- pf_band_energy("audio.wav", times = c(0.10, 0.15), f_min = 0, f_max = 1000)
plot(pf_spectrogram("audio.wav"))             # "wide" (default), "narrow", or seconds

See r-package/README.md for details, including the batch interface (pf_analyze) that computes several measures from one file load.

Any language (JSON pipe)

The praatfan-open-pipe binary reads one JSON request on stdin and writes one JSON response to stdout — usable from any language that can spawn a process:

cd rust && cargo build --release --features pipe --bin praatfan-open-pipe

echo '{"wav_path":"audio.wav","analyses":[{"type":"pitch_ac"}]}' \
  | rust/target/release/praatfan-open-pipe

praatfan-open-pipe --help documents the full request/response schema.


Python Usage

Creating Sound Objects

import praatfan
import numpy as np

# From a WAV file
sound = praatfan.Sound("path/to/audio.wav")

# From a numpy array
samples = np.sin(2 * np.pi * 440 * np.arange(44100) / 44100)
sound = praatfan.Sound(samples, sampling_frequency=44100)

# From a specific channel of a multi-channel file
sound = praatfan.Sound.from_file_channel("stereo.wav", channel=0)  # left channel

Sound properties:

sound.n_samples          # Number of samples
sound.sampling_frequency # Sample rate in Hz
sound.duration           # Duration in seconds
sound.values()           # Samples as numpy array
sound.xs()               # Time values for each sample

Pitch Analysis

Detect fundamental frequency (F0) using autocorrelation or cross-correlation methods.

# Autocorrelation method (default, more accurate for clean speech)
pitch = sound.to_pitch_ac(
    time_step=0.0,        # 0 = auto (0.75 / pitch_floor)
    pitch_floor=75.0,     # Minimum pitch in Hz
    pitch_ceiling=600.0   # Maximum pitch in Hz
)

# Cross-correlation method (better for noisy speech)
pitch = sound.to_pitch_cc(time_step=0.0, pitch_floor=75.0, pitch_ceiling=600.0)

# Alias for to_pitch_ac
pitch = sound.to_pitch()

Pitch properties and methods:

pitch.n_frames                    # Number of analysis frames
pitch.time_step                   # Time step between frames

# Get all values as numpy arrays
times = pitch.xs()                # Time points for all frames
frequencies = pitch.values()      # F0 values (NaN for unvoiced frames)
frequencies = pitch.to_array()    # Same as values()

# Parselmouth-compatible selected_array property
data = pitch.selected_array       # Dict with 'frequency' and 'strength' keys
f0 = data['frequency']            # numpy array of F0 values
strength = data['strength']       # numpy array of correlation strengths

# Access individual frames (0-indexed)
f0 = pitch.get_value_in_frame(10)         # F0 at frame 10
f0 = pitch.get_value_at_time(0.5)         # F0 at t=0.5s (interpolated)
strength = pitch.get_strength_at_time(0.5) # Strength at t=0.5s

Formant Analysis

Extract formant frequencies and bandwidths using Burg's LPC method.

formant = sound.to_formant_burg(
    time_step=0.0,              # 0 = auto
    max_number_of_formants=5,   # Maximum formants to track
    maximum_formant=5500.0,     # Max formant frequency (5500 for male, 5000 for female)
    window_length=0.025,        # Window length in seconds
    pre_emphasis_from=50.0      # Pre-emphasis frequency in Hz
)

Formant properties and methods:

formant.n_frames                  # Number of analysis frames
formant.time_step                 # Time step between frames

# Get formant values as numpy arrays (formant_number: 1=F1, 2=F2, etc.)
times = formant.xs()              # Time points
f1 = formant.to_array(1)          # F1 values for all frames
f2 = formant.to_array(2)          # F2 values
b1 = formant.to_bandwidth_array(1) # B1 (bandwidth of F1)

# Access individual frames
f1 = formant.get_value_in_frame(1, 10)      # F1 at frame 10
f1 = formant.get_value_at_time(1, 0.5)      # F1 at t=0.5s
b1 = formant.get_bandwidth_in_frame(1, 10)  # B1 at frame 10
b1 = formant.get_bandwidth_at_time(1, 0.5)  # B1 at t=0.5s
n = formant.get_number_of_formants(10)      # Number of formants at frame 10

Intensity Analysis

Compute RMS energy contour in dB.

intensity = sound.to_intensity(
    minimum_pitch=100.0,  # Determines window size
    time_step=0.0         # 0 = auto
)

Intensity properties and methods:

intensity.n_frames                # Number of frames
intensity.time_step               # Time step

times = intensity.xs()            # Time points
values = intensity.values()       # Intensity in dB
values = intensity.to_array()     # Same as values()

# Statistics
intensity.get_minimum()           # Min intensity in dB
intensity.get_maximum()           # Max intensity in dB
intensity.get_mean()              # Mean intensity in dB

# Access at specific time
db = intensity.get_value_at_time(0.5)

Harmonicity Analysis

Compute harmonics-to-noise ratio (HNR).

# Autocorrelation method
harmonicity = sound.to_harmonicity_ac(
    time_step=0.01,           # Time step in seconds
    minimum_pitch=75.0,       # Minimum pitch in Hz
    silence_threshold=0.1,    # Silence threshold (0-1)
    periods_per_window=4.5    # Periods per analysis window (AC minimum: 3.0)
)

# Cross-correlation method
harmonicity = sound.to_harmonicity_cc(
    time_step=0.01,
    minimum_pitch=75.0,
    silence_threshold=0.1,
    periods_per_window=1.0
)

# Alias for to_harmonicity_ac
harmonicity = sound.to_harmonicity()

Harmonicity properties and methods:

harmonicity.n_frames              # Number of frames
harmonicity.time_step             # Time step

times = harmonicity.xs()          # Time points
values = harmonicity.values()     # HNR in dB (-200 = no periodicity; nan = correlation unresolvable from 1)
values = harmonicity.to_array()   # Same as values()

hnr = harmonicity.get_value_at_time(0.5)

Spectrum Analysis

Compute single-frame FFT with spectral moments.

spectrum = sound.to_spectrum(fast=True)  # fast=True uses power-of-2 FFT

Spectrum properties and methods:

spectrum.n_bins                   # Number of frequency bins
spectrum.df                       # Frequency resolution (Hz)
spectrum.maximum_frequency        # Maximum frequency (Nyquist)

freqs = spectrum.xs()             # Frequency values for all bins
real_part = spectrum.real()       # Real parts
imag_part = spectrum.imag()       # Imaginary parts

# Spectral moments
cog = spectrum.get_center_of_gravity(power=2.0)  # Centroid in Hz
std = spectrum.get_standard_deviation(power=2.0) # Spread in Hz
skew = spectrum.get_skewness(power=2.0)          # Skewness
kurt = spectrum.get_kurtosis(power=2.0)          # Kurtosis

# Band energy
energy = spectrum.get_band_energy(band_floor=0.0, band_ceiling=1000.0)

Spectrogram Analysis

Compute time-frequency representation (STFT).

spectrogram = sound.to_spectrogram(
    window_length=0.005,      # Window length in seconds
    maximum_frequency=5000.0, # Maximum frequency in Hz
    time_step=0.002,          # Time step in seconds
    frequency_step=20.0       # Frequency step in Hz
)

Spectrogram properties and methods:

spectrogram.n_times               # Number of time frames
spectrogram.n_freqs               # Number of frequency bins
spectrogram.time_step             # Time step
spectrogram.frequency_step        # Frequency step

times = spectrogram.xs()          # Time values
freqs = spectrogram.ys()          # Frequency values
power = spectrogram.values()      # Power values (flat array, freq × time)

# Access at specific location
power = spectrogram.get_power_at(time=0.5, frequency=1000.0)

JavaScript/WASM Usage

Setup

// ES modules (web)
import init, { Sound } from './pkg/praatfan.js';

async function main() {
    await init();  // Initialize WASM module

    // Now you can use Sound and other types
}

// Node.js
const { Sound } = require('./pkg/praatfan.js');

Creating Sound Objects

// From raw samples (Float64Array)
const samples = new Float64Array([...]);
const sound = new Sound(samples, 44100);

// From WAV file bytes (Uint8Array)
const response = await fetch('audio.wav');
const wavBytes = new Uint8Array(await response.arrayBuffer());
const sound = Sound.from_wav(wavBytes);

// From specific channel of multi-channel WAV
const sound = Sound.from_wav_channel(wavBytes, 0);  // left channel

Sound properties:

sound.n_samples()     // Number of samples
sound.sample_rate()   // Sample rate in Hz
sound.duration()      // Duration in seconds
sound.samples()       // Float64Array of samples

Pitch Analysis

// Autocorrelation method
const pitch = sound.to_pitch_ac(
    0.0,    // time_step (0 = auto)
    75.0,   // pitch_floor
    600.0   // pitch_ceiling
);

// Cross-correlation method
const pitch = sound.to_pitch_cc(0.0, 75.0, 600.0);

Pitch methods:

pitch.n_frames()              // Number of frames
pitch.time_step()             // Time step between frames
pitch.pitch_floor()           // Minimum pitch
pitch.pitch_ceiling()         // Maximum pitch

const times = pitch.times();       // Float64Array of time points
const values = pitch.values();     // Float64Array of F0 (NaN for unvoiced)
const strengths = pitch.strengths(); // Float64Array of correlation strengths

// Access individual frames
const time = pitch.get_time_from_frame(10);
const f0 = pitch.get_value_in_frame(10);  // NaN if unvoiced

Formant Analysis

const formant = sound.to_formant_burg(
    0.0,     // time_step
    5,       // max_num_formants
    5500.0,  // max_formant_hz
    0.025,   // window_length
    50.0     // pre_emphasis_from
);

Formant methods:

formant.n_frames()            // Number of frames
formant.time_step()           // Time step
formant.max_num_formants()    // Maximum formants per frame

const times = formant.times();           // Float64Array
const f1 = formant.formant_values(1);    // Float64Array of F1
const f2 = formant.formant_values(2);    // Float64Array of F2
const b1 = formant.bandwidth_values(1);  // Float64Array of B1

// Access individual frames
const time = formant.get_time_from_frame(10);
const f1 = formant.get_value_at_frame(10, 1);     // F1 at frame 10
const b1 = formant.get_bandwidth_at_frame(10, 1); // B1 at frame 10

Intensity Analysis

const intensity = sound.to_intensity(
    100.0,  // min_pitch
    0.0     // time_step
);

Intensity methods:

intensity.n_frames()          // Number of frames
intensity.time_step()         // Time step

const times = intensity.times();   // Float64Array
const values = intensity.values(); // Float64Array (dB)

intensity.get_minimum()       // Min intensity
intensity.get_maximum()       // Max intensity
intensity.get_mean()          // Mean intensity

const time = intensity.get_time_from_frame(10);
const db = intensity.get_value_in_frame(10);

Harmonicity Analysis

// Autocorrelation method
const harmonicity = sound.to_harmonicity_ac(
    0.01,   // time_step
    75.0,   // min_pitch
    0.1,    // silence_threshold
    4.5     // periods_per_window
);

// Cross-correlation method
const harmonicity = sound.to_harmonicity_cc(0.01, 75.0, 0.1, 1.0);

Harmonicity methods:

harmonicity.n_frames()        // Number of frames
harmonicity.time_step()       // Time step

const times = harmonicity.times();   // Float64Array
const values = harmonicity.values(); // Float64Array (dB; -200 = no periodicity, NaN = undefined)

const time = harmonicity.get_time_from_frame(10);
const hnr = harmonicity.get_value_in_frame(10);

Spectrum Analysis

const spectrum = sound.to_spectrum(true);  // fast=true

Spectrum methods:

spectrum.n_bins()             // Number of frequency bins
spectrum.df()                 // Frequency resolution (Hz)
spectrum.max_frequency()      // Maximum frequency

const real = spectrum.real();  // Float64Array
const imag = spectrum.imag();  // Float64Array

const freq = spectrum.get_freq_from_bin(100);

// Spectral moments
const cog = spectrum.get_center_of_gravity(2.0);
const std = spectrum.get_standard_deviation(2.0);
const skew = spectrum.get_skewness(2.0);
const kurt = spectrum.get_kurtosis(2.0);

// Band energy
const energy = spectrum.get_band_energy(0.0, 1000.0);

Spectrogram Analysis

const spectrogram = sound.to_spectrogram(
    0.005,   // window_length
    5000.0,  // max_frequency
    0.002,   // time_step
    20.0     // frequency_step
);

Spectrogram methods:

spectrogram.n_times()         // Number of time frames
spectrogram.n_freqs()         // Number of frequency bins
spectrogram.time_step()       // Time step
spectrogram.freq_step()       // Frequency step
spectrogram.time_min()        // Start time
spectrogram.time_max()        // End time
spectrogram.freq_min()        // Minimum frequency
spectrogram.freq_max()        // Maximum frequency

const times = spectrogram.times();        // Float64Array
const freqs = spectrogram.frequencies();  // Float64Array
const values = spectrogram.values();      // Float64Array (flat, freq × time)

const time = spectrogram.get_time_from_frame(10);
const freq = spectrogram.get_freq_from_bin(50);
const power = spectrogram.get_value_at(10, 50);  // time_frame, freq_bin

API Comparison: Python vs JavaScript

Feature Python JavaScript
Create from samples Sound(samples, sampling_frequency=sr) new Sound(samples, sr)
Create from WAV file Sound("path.wav") Sound.from_wav(bytes)
Get samples sound.values() sound.samples()
Sample rate sound.sampling_frequency sound.sample_rate()
Pitch F0 values pitch.values() or pitch.selected_array['frequency'] pitch.values()
Formant values formant.to_array(n) formant.formant_values(n)
Time points .xs() .times()

Performance and threading

The Rust backend (praatfan_rust) spreads the per-frame work of pitch, harmonicity, intensity, formant and spectrogram analysis, and the resampler behind formant analysis, across all CPU cores. Results are bit-identical to a single-threaded run at any thread count.

  • Thread count: set the RAYON_NUM_THREADS environment variable before the first analysis in a process (e.g. RAYON_NUM_THREADS=4). If you already parallelise across files (multiprocessing, joblib, several R sessions), set it to 1, or to cores ÷ workers, so the two levels don't oversubscribe the CPU.
  • Python threads: the GIL is released while an analysis runs, so calls from several Python threads run concurrently.
  • multiprocessing: safe with the fork start method, including after the parent process has already run analyses.
  • WebAssembly builds are single-threaded.

Backends and auto-selection (Python)

With no set_backend(), PRAATFAN_BACKEND or config file, the first available of these is used:

Backend Install Speed
praatfan_rust pip install praatfan-rust fastest (native, all cores)
praatfan_gpl pip install praatfan-gpl native
praatfan_wasm pip install "praatfan[wasm]" the praatfan_rust engine compiled to WebAssembly and run in an embedded V8 (mini-racer); one thread per call, several times faster than pure Python
praatfan always installed pure-Python reference implementation

parselmouth is never auto-selected; request it by name (set_backend("parselmouth") or PRAATFAN_BACKEND=parselmouth).

praatfan_wasm runs the analyses the JSON pipe carries — pitch (AC/CC), formant (Burg), intensity, harmonicity (AC/CC), spectral moments and band energy at times — in WebAssembly, with the same numbers as praatfan_rust to floating-point rounding. Everything else (to_spectrum, to_spectrogram, the *_referenced analyses, extract_part, resample) is served by the pure-Python implementation, and Pitch.raw_ac_strengths() raises NotImplementedError. Unvoiced F0 is NaN, as on every backend. Each process gets its own engine; multiprocessing works with every start method (a process forked after its parent has used the engine routes its calls to a helper process, since V8 itself does not survive fork).

Common Workflows

Extract F1/F2 vowel formants

Python:

import praatfan
import numpy as np

sound = praatfan.Sound("vowel.wav")
formant = sound.to_formant_burg(max_number_of_formants=5, maximum_formant=5500)

# Get F1 and F2 trajectories
f1 = formant.to_array(1)
f2 = formant.to_array(2)
times = formant.xs()

# Filter out undefined values
valid = ~(np.isnan(f1) | np.isnan(f2))
print(f"Mean F1: {np.mean(f1[valid]):.0f} Hz")
print(f"Mean F2: {np.mean(f2[valid]):.0f} Hz")

JavaScript:

const sound = Sound.from_wav(wavBytes);
const formant = sound.to_formant_burg(0, 5, 5500, 0.025, 50);

const f1 = formant.formant_values(1);
const f2 = formant.formant_values(2);

// Filter and compute mean
let sumF1 = 0, sumF2 = 0, count = 0;
for (let i = 0; i < f1.length; i++) {
    if (!isNaN(f1[i]) && !isNaN(f2[i])) {
        sumF1 += f1[i];
        sumF2 += f2[i];
        count++;
    }
}
console.log(`Mean F1: ${(sumF1/count).toFixed(0)} Hz`);
console.log(`Mean F2: ${(sumF2/count).toFixed(0)} Hz`);

Pitch tracking with voicing detection

Python:

sound = praatfan.Sound("speech.wav")
pitch = sound.to_pitch_ac(pitch_floor=75, pitch_ceiling=300)

f0 = pitch.selected_array['frequency']
times = pitch.xs()

# Find voiced regions
voiced = ~np.isnan(f0)
print(f"Voiced frames: {voiced.sum()} / {len(f0)}")
print(f"Mean F0 (voiced): {np.nanmean(f0):.1f} Hz")

JavaScript:

const sound = Sound.from_wav(wavBytes);
const pitch = sound.to_pitch_ac(0, 75, 300);

const f0 = pitch.values();
const times = pitch.times();

let voicedCount = 0, sum = 0;
for (let i = 0; i < f0.length; i++) {
    if (!isNaN(f0[i])) {
        voicedCount++;
        sum += f0[i];
    }
}
console.log(`Voiced frames: ${voicedCount} / ${f0.length}`);
console.log(`Mean F0 (voiced): ${(sum/voicedCount).toFixed(1)} Hz`);

Typical Parameter Values

Analysis Parameter Typical Value Notes
Pitch pitch_floor 75 Hz Lower for male voices
Pitch pitch_ceiling 300-600 Hz Higher for female/child voices
Formant maximum_formant 5500 Hz (male), 5000 Hz (female) Adjust for speaker
Formant max_number_of_formants 5 Standard for vowel analysis
Formant window_length 0.025 s 25 ms is typical
Intensity minimum_pitch 100 Hz Determines window size
Harmonicity periods_per_window 4.5 (AC), 1.0 (CC) AC requires >= 3.0 (FutureWarning below it, error in a future release); 1.0 is the CC value, not an AC value

Notes

  • Mono audio only: praatfan only supports mono audio. Use from_file_channel() or from_wav_channel() for stereo files.
  • Undefined values: Unvoiced frames return NaN for pitch. Harmonicity uses two distinct markers: -200 dB where no periodicity was found (unvoiced, silence, DC), and NaN where the correlation is not resolvable from 1 and no HNR can be derived. Filter with np.isfinite(v) & (v > -200).
  • Time step = 0: When time_step is 0, an automatic value is computed based on the analysis type.
  • Parselmouth compatibility: The Python API is designed to be compatible with parselmouth's API, making migration straightforward.

Metadata

Release files for praatfan 0.1.13

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for praatfan 0.1.13
File Size Uploaded Provenance
praatfan-0.1.13.tar.gz 1.7 MB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for praatfan 0.1.13
File Interpreter ABI Platform
praatfan-0.1.13-py3-none-any.whl Python 3 none any Details

Total release size: 2.5 MB

Release files / praatfan-0.1.13.tar.gz

Download URL praatfan-0.1.13.tar.gz
Size 1.7 MB
Tags Source
SHA-256 checksum
How to use checksums
77fc5ffdb7a0b8ca2b9fe3d492fa3391c99ca4de8918b56834da072c4044eb53
BLAKE2b-256 checksum
How to use checksums
6be7b48ad6ad18cc054862363fe4c18cf87eea5e6e567e57f5921f18af8582d3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.12

Release files / praatfan-0.1.13-py3-none-any.whl

Download URL praatfan-0.1.13-py3-none-any.whl
Size 848.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
95a4316fb46678b50b518ababfd50b6e03702ab3a358c9eae625bc17246bbe52
BLAKE2b-256 checksum
How to use checksums
4856987d2b90315f59d1d26a76409b2739be84f18141c41dee1b60fe68b46517
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.12

Security for praatfan 0.1.13

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page