Skip to contents

Overview

This vignette demonstrates how to perform integrated phonetic analysis workflows using the pladdrr package. We will cover:

  • TextGrid-based segmentation: Create and manipulate annotations
  • Sound manipulation: Extract, resample, and normalize audio
  • Acoustic measurements: F0, formants, intensity, and voice quality
  • Batch processing: Analyze multiple segments efficiently
  • Data export: Prepare data for statistical analysis in R

This workflow is typical in phonetic research, where annotated speech data requires systematic acoustic analysis.

Background

TextGrid Annotation

TextGrids are Praat’s standard format for time-aligned annotations. They consist of:

  • Interval tiers: Segments with start/end times and labels (e.g., words, phones)
  • Point tiers: Time points with labels (e.g., landmarks, events)

TextGrids enable:

  • Manual or automatic (forced alignment) annotation
  • Multi-level linguistic annotation (phones, syllables, words, utterances)
  • Integration with acoustic analysis

The Phonetic Analysis Pipeline

A typical phonetic analysis workflow:

  1. Record and preprocess audio
  2. Annotate with TextGrid (manual or via forced alignment)
  3. Segment audio based on annotations
  4. Extract acoustic features for each segment
  5. Analyze patterns statistically

The pladdrr package provides all necessary tools for steps 2-5.

Part 1: Loading and Creating Data

Creating Synthetic Audio

For demonstration, we create a 5-second audio signal. In real research, you would load existing recordings.

library(pladdrr)
#> pladdrr: direct access to Praat's core algorithms from R.
#> See ?pladdrr for an overview, or citation("pladdrr") for citation details.

# Create synthetic audio (440 Hz tone)
sound <- Sound$create_tone(
  duration = 5.0,
  frequency = 440,
  sampling_rate = 16000,
  amplitude = 0.5
)

# Inspect audio properties
cat("Duration:", sound$get_duration(), "seconds\n")
#> Duration: 5 seconds
cat("Sample rate:", sound$get_sampling_frequency(), "Hz\n")
#> Sample rate: 16000 Hz
cat("Channels:", sound$get_number_of_channels(), "\n")
#> Channels: 1

Creating a TextGrid

Create a TextGrid with word and phone annotations:

# Create TextGrid with 2 interval tiers
tg <- TextGrid$create(
  tmin = 0,
  tmax = 5,
  tier_names = "words phones",
  point_tiers = ""  # Both are interval tiers
)

# Add word boundaries
tg$insert_boundary(1, 1.0)
tg$insert_boundary(1, 2.5)
tg$insert_boundary(1, 4.0)

# Label word intervals
tg$set_interval_text(1, 1, "silence")
tg$set_interval_text(1, 2, "hello")
tg$set_interval_text(1, 3, "world")
tg$set_interval_text(1, 4, "silence")

# Add phone boundaries (finer granularity)
phone_times <- c(1.0, 1.3, 1.7, 2.0, 2.5, 3.0, 3.5, 4.0)
for (t in phone_times) {
  tg$insert_boundary(2, t)
}

# Label phone intervals
phone_labels <- c("", "h", "E", "l", "oU", "w", "3", "ld", "")
for (i in seq_along(phone_labels)) {
  tg$set_interval_text(2, i, phone_labels[i])
}

cat("TextGrid created with", tg$get_number_of_tiers(), "tiers\n")
#> TextGrid created with 2 tiers

Plotting the TextGrid

autoplot( ) and autolayer() methods let you compose TextGrid tier displays with ggplot2:

p <- ggplot2::autoplot(tg)
p2 <- p + ggplot2::autolayer(tg, tier = 2, color = "darkred")
class(p2)
#> [1] "ggplot2::ggplot" "ggplot"          "ggplot2::gg"     "S7_object"      
#> [5] "gg"

Part 2: TextGrid Queries

Exploring Tier Structure

# Get tier information
tier_names <- tg$get_tier_names()
for (i in 1:tg$get_number_of_tiers()) {
  tier_type <- if (tg$tier_is_interval_tier(i)) "IntervalTier" else "PointTier"
  n_items <- tg$get_number_of_intervals(i)
  cat(sprintf("Tier %d: %s (%s) - %d items\n", 
              i, tier_names[i], tier_type, n_items))
}
#> Tier 1: words (IntervalTier) - 4 items
#> Tier 2: phones (IntervalTier) - 9 items

Extracting Interval Information

# Get all intervals from the phones tier
phone_tier <- 2
n_intervals <- tg$get_number_of_intervals(phone_tier)

# Create a data frame with interval information
phone_data <- data.frame(
  interval = 1:n_intervals,
  start = numeric(n_intervals),
  end = numeric(n_intervals),
  duration = numeric(n_intervals),
  label = character(n_intervals),
  stringsAsFactors = FALSE
)

for (i in 1:n_intervals) {
  phone_data$start[i] <- tg$get_interval_start_time(phone_tier, i)
  phone_data$end[i] <- tg$get_interval_end_time(phone_tier, i)
  phone_data$duration[i] <- phone_data$end[i] - phone_data$start[i]
  phone_data$label[i] <- tg$get_interval_text(phone_tier, i)
}

print(phone_data)
#>   interval start end duration label
#> 1        1   0.0 1.0      1.0      
#> 2        2   1.0 1.3      0.3     h
#> 3        3   1.3 1.7      0.4     E
#> 4        4   1.7 2.0      0.3     l
#> 5        5   2.0 2.5      0.5    oU
#> 6        6   2.5 3.0      0.5     w
#> 7        7   3.0 3.5      0.5     3
#> 8        8   3.5 4.0      0.5    ld
#> 9        9   4.0 5.0      1.0

Part 3: Sound Manipulation

Extracting Audio Segments

Extract specific portions of audio based on TextGrid intervals:

# Extract the "hello" word (interval 2 of word tier)
hello_start <- tg$get_interval_start_time(1, 2)
hello_end <- tg$get_interval_end_time(1, 2)

hello_sound <- sound$extract_part(
  from_time = hello_start,
  to_time = hello_end,
  preserve_times = FALSE
)

cat("Extracted 'hello':", hello_sound$get_total_duration(), "seconds\n")
#> Extracted 'hello': 1.5 seconds

Resampling

Change sampling rate for analysis or compatibility:

# Resample to 8 kHz (e.g., for telephony simulations)
sound_8k <- sound$resample(new_frequency = 8000, precision = 50)
cat("Resampled to", sound_8k$get_sampling_frequency(), "Hz\n")
#> Resampled to 8000 Hz

Intensity Normalization

Normalize loudness across recordings:

# Scale to 70 dB SPL
sound_normalized <- sound$scale_intensity(new_intensity = 70)
cat("Normalized to 70 dB\n")
#> Normalized to 70 dB

Pre-emphasis Filtering

Apply pre-emphasis for formant analysis:

# Pre-emphasize from 50 Hz
sound_preemph <- sound$pre_emphasize(from_frequency = 50)
cat("Applied pre-emphasis from 50 Hz\n")
#> Applied pre-emphasis from 50 Hz

Part 4: Acoustic Analysis

Fundamental Frequency (F0) Extraction

# Extract pitch contour
pitch <- sound$to_pitch(
  time_step = 0.01,
  pitch_floor = 75,
  pitch_ceiling = 600
)

# Get statistics
f0_mean <- pitch$get_mean(from_time = 0, to_time = 0, unit = "hertz")
f0_sd <- pitch$get_standard_deviation(from_time = 0, to_time = 0,
  unit = "hertz")

cat(sprintf("F0: Mean = %.1f Hz, SD = %.1f Hz\n", f0_mean, f0_sd))
#> F0: Mean = 440.0 Hz, SD = 0.0 Hz

Formant Analysis

# Extract formants using Burg's algorithm
formant <- sound$to_formant_burg(
  time_step = 0.01,
  max_formants = 5,
  max_frequency = 5500,
  window_length = 0.025,
  pre_emphasis_from = 50
)

# Measure formants at specific time point (e.g., vowel midpoint)
vowel_time <- 1.5  # Middle of "E" vowel
f1 <- formant$get_value_at_time(formant_number = 1, time = vowel_time,
  unit = "hertz")
f2 <- formant$get_value_at_time(formant_number = 2, time = vowel_time,
  unit = "hertz")
f3 <- formant$get_value_at_time(formant_number = 3, time = vowel_time,
  unit = "hertz")

cat(sprintf("Formants at %.2f s: F1 = %.0f Hz, F2 = %.0f Hz, F3 = %.0f Hz\n",
            vowel_time, f1, f2, f3))
#> Formants at 1.50 s: F1 = 404 Hz, F2 = 443 Hz, F3 = 481 Hz

Intensity

# Extract intensity contour
intensity <- sound$to_intensity(
  minimum_pitch = 100,
  time_step = 0.01
)

int_mean <- intensity$get_mean(from_time = 0, to_time = 0)
cat(sprintf("Mean intensity: %.1f dB\n", int_mean))
#> Mean intensity: 54.7 dB

Voice Quality (HNR)

# Harmonics-to-Noise Ratio
harmonicity <- sound$to_harmonicity_cc(
  time_step = 0.01,
  min_pitch = 75,
  silence_threshold = 0.1,
  periods_per_window = 1.0
)

hnr_mean <- harmonicity$get_mean(from_time = 0, to_time = 0)
cat(sprintf("Mean HNR: %.1f dB\n", hnr_mean))
#> Mean HNR: 80.5 dB

Part 5: Batch Processing

Process all vowel segments systematically:

# Identify vowel intervals: labels containing an uppercase vowel letter
# (X-SAMPA tense-vowel convention, e.g. "E", "oU") or "3" (rhotic vowel, as in
#  "world")
vowel_intervals <- grep("[AEIOUY3]", phone_data$label, ignore.case = FALSE)

# Extract acoustic features for each vowel
vowel_features <- data.frame(
  phone = character(),
  start = numeric(),
  end = numeric(),
  duration = numeric(),
  f0_mean = numeric(),
  f1 = numeric(),
  f2 = numeric(),
  f3 = numeric(),
  intensity = numeric(),
  hnr = numeric(),
  stringsAsFactors = FALSE
)

for (idx in vowel_intervals) {
  label <- phone_data$label[idx]
  start <- phone_data$start[idx]
  end <- phone_data$end[idx]
  midpoint <- start + (end - start) / 2
  
  # Extract features at vowel midpoint
  f0 <- pitch$get_value_at_time(time = midpoint, unit = "hertz",
    interpolate = TRUE)
  f1 <- formant$get_value_at_time(1, midpoint, "hertz")
  f2 <- formant$get_value_at_time(2, midpoint, "hertz")
  f3 <- formant$get_value_at_time(3, midpoint, "hertz")
  int <- intensity$get_value_at_time(midpoint, interpolation = "cubic")
  hnr <- harmonicity$get_value_at_time(midpoint, interpolation = "linear")
  
  vowel_features <- rbind(vowel_features, data.frame(
    phone = label,
    start = start,
    end = end,
    duration = end - start,
    f0_mean = if (is.na(f0)) NA else f0,
    f1 = f1,
    f2 = f2,
    f3 = f3,
    intensity = int,
    hnr = hnr
  ))
}

print(vowel_features)
#>   phone start end duration  f0_mean       f1       f2       f3 intensity
#> 1     E   1.3 1.7      0.4 439.9998 404.4790 442.5576 480.6363  54.71009
#> 2    oU   2.0 2.5      0.5 439.9998 404.6389 442.7037 480.7712  54.71009
#> 3     3   3.0 3.5      0.5 439.9998 404.4781 442.5568 480.6356  54.71009
#>        hnr
#> 1 80.08884
#> 2 80.08884
#> 3 80.08884

Part 6: Data Export and Visualization

Export to CSV

# Save for statistical analysis (tempdir() here so the vignette writes nothing
# into your working directory; use a real path in your own scripts)
write.csv(
  vowel_features,
  file.path(tempdir(), "vowel_acoustic_features.csv"),
  row.names = FALSE
)

Visualization

pladdrr provides autoplot( )/autolayer() methods (shown above for TextGrid) plus plot() methods for Sound, Pitch, Formant, Intensity, and other analysis objects. For vowel spaces, formant trajectories, pitch/intensity contours, spectrograms, and voice quality reports, see vignette("visualization").

Real-World Applications

1. Sociolinguistic Research

Analyze vowel variation across speakers, dialects, or social groups:

# Load multiple speakers
speakers <- c("speaker1.wav", "speaker2.wav", "speaker3.wav")
textgrids <- c("speaker1.TextGrid", "speaker2.TextGrid", "speaker3.TextGrid")

# Process all speakers
all_data <- data.frame()
for (i in seq_along(speakers)) {
  sound <- Sound$new(speakers[i])
  tg <- TextGrid$new(textgrids[i])
  # ... extract features ...
  all_data <- rbind(all_data, features)
}

# Statistical analysis
model <- lm(f1 ~ vowel * speaker, data = all_data)

2. L2 Acquisition Studies

Track vowel development in second language learners:

# Longitudinal data (same speaker, multiple sessions)
sessions <- c("session1.wav", "session2.wav", "session3.wav")

# Measure vowel space area over time
# ... (see Example 9 for vowel space calculations) ...

3. Clinical Voice Assessment

Multi-dimensional voice profiling:

# Extract voice quality metrics
pointprocess <- sound$to_point_process_periodic_cc(pitch_floor = 75,
  pitch_ceiling = 300)
voice_profile <- data.frame(
  f0_mean = pitch$get_mean(),
  f0_sd = pitch$get_standard_deviation(),
  jitter = pointprocess$get_jitter_local(),
  shimmer = pointprocess$get_shimmer_local(sound),
  hnr = harmonicity$get_mean(),
  intensity_mean = intensity$get_mean()
)

# Add cepstral peak prominence smoothed (CPPS)
# CPPS is sensitive to parameter choice - see example for details
cepstrogram <- sound$to_powercepstrogram(
  pitch_floor = 60,
  time_step = 0.002
)
voice_profile$cpps <- cepstrogram$get_cpps(
  subtract_tilt = FALSE,  # Match Praat default
  time_averaging_window = 0.001,
  quefrency_averaging_window = 0.0005,
  pitch_floor = 60,
  pitch_ceiling = 330
)

Note: CPPS values are sensitive to parameters. See system.file( "examples", "10_cpps_analysis.R", package = "pladdrr") for parameter effects and clinical interpretation.

Best Practices

1. Pre-emphasis for Formants

Always pre-emphasize before formant analysis:

sound_preemph <- sound$pre_emphasize(from_frequency = 50)
formant <- sound_preemph$to_formant_burg(...)

2. Appropriate Analysis Parameters

Choose parameters based on speaker characteristics:

# Adult male
pitch <- sound$to_pitch(pitch_floor = 75, pitch_ceiling = 300)
formant <- sound$to_formant_burg(max_frequency = 5000)

# Adult female
pitch <- sound$to_pitch(pitch_floor = 100, pitch_ceiling = 500)
formant <- sound$to_formant_burg(max_frequency = 5500)

# Child
pitch <- sound$to_pitch(pitch_floor = 200, pitch_ceiling = 700)
formant <- sound$to_formant_burg(max_frequency = 8000)

3. Handle Missing Values

Acoustic measurements may fail:

f0 <- pitch$get_value_at_time(time, "hertz", interpolate = TRUE)
if (is.na(f0)) {
  # Handle voiceless segments or tracking failures
  cat("Warning: No F0 at time", time, "\n")
}

4. Efficient Batch Processing

For large corpora, use vectorization and sampling:

# Sample large datasets
if (nrow(data) > 10000) {
  data <- data[sample(nrow(data), 1000), ]  # Random sample
}

Summary

The pladdrr package enables:

  • Annotation workflows with TextGrid creation and manipulation

  • Sound processing (segmentation, resampling, filtering)

  • Multi-dimensional acoustic analysis (prosody, voice quality, spectral features)

  • R integration for statistical analysis and visualization via data.frame output and `autoplot(

    )/autolayer()` methods

For more examples, see:

References

  • Boersma, P., & Weenink, D. ( 2023). Praat: doing phonetics by computer. https://praat.org/
  • Thomas, E. R., & Kendall, T. ( 2007). NORM: The vowel normalization and plotting suite.