Integrated Phonetic Analysis with pladdrr
pladdrr Package Authors
2026-09-10
Source:vignettes/integrated-phonetic-analysis.Rmd
integrated-phonetic-analysis.RmdOverview
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:
- Record and preprocess audio
- Annotate with TextGrid (manual or via forced alignment)
- Segment audio based on annotations
- Extract acoustic features for each segment
- 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: 1Creating 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 tiersPlotting the TextGrid
autoplot( ) and autolayer() methods let
you compose TextGrid tier displays with ggplot2:
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 itemsExtracting 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.0Part 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 secondsResampling
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 HzIntensity 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 dBPre-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 HzPart 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 HzFormant 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 HzPart 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.08884Part 6: Data Export and Visualization
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)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.frameoutput and `autoplot()
/autolayer()` methods
For more examples, see:
-
vignette("vowel-space-analysis")- F1-F2 analysis and normalization -
vignette("textgrid-workflows")- Advanced TextGrid manipulation -
inst/examples/- Complete workflow scripts
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.