Generated banner for Matlab Analyze Spectrum
Generated logo for Matlab Analyze Spectrum

Matlab Analyze Spectrum

MathWorksOfficialCreated by MathWorks

  • Skill
  • Tool integration
  • Science and research
  • BSD-2-Clause
  • No issues found

From the author

Analyze signal spectra in MATLAB using nonparametric and parametric estimators. Use when computing frequency content, PSD, power spectrum, spectral peaks, bandwidth, or streaming spectral analysis. Covers pspectrum, pwelch, periodogram, pmtm, pburg, pmusic, rootmusic, plomb, poctave, spectrumAnalyzer, dsp.SpectrumEstimator. TRIGGER: FFT, PSD, power spectrum, spectral analysis, frequency content, bandwidth measurement, spectral leakage, window selection, streaming spectrum, periodicity, resolve close frequencies, multitaper. DO NOT TRIGGER: filter design, spectrogram/time-frequency, audio features.
— the description by MathWorks

AutoGineer summary

Written by AutoGineer from the skill’s own files, not by the author

This skill provides a structured approach to spectral analysis in MATLAB, covering both batch and streaming data processing. It guides users through selecting the appropriate estimator (e.g., pspectrum, pwelch, periodogram, pmtm, pburg) based on signal characteristics like length, uniformity, and spectral content. The skill emphasizes best practices such as proper window selection, unit normalization, and the use of built-in measurement functions for peak detection, bandwidth, and power estimation, while discouraging manual FFT implementations.

What it does

  • Recommends spectral estimators based on signal length and character
  • Provides workflows for batch and streaming spectral analysis
  • Automates peak detection, bandwidth measurement, and power calculation
  • Offers guidance on window selection and leakage control
  • Supports advanced techniques like multitaper and parametric estimation
  • Ensures correct unit normalization and dB conversion

When to use it

  • When computing frequency content, PSD, or power spectrum
  • When analyzing signal periodicity or resolving close frequencies
  • When performing real-time streaming spectral analysis
  • When comparing frequency content between two signals
  • When measuring band power or occupied bandwidth

Advantages

  • Reduces errors by using built-in, calibrated functions
  • Provides clear decision trees for estimator selection
  • Standardizes spectral visualization and measurement
  • Includes verification steps like Parseval's theorem
  • Handles complex scenarios like non-uniform sampling

Usage examples

“How do I compute the PSD of a long signal in MATLAB?”
The agent will recommend using pwelch for low-variance PSD estimation, providing code to set segment length and windowing for optimal resolution.
“I need to find the dominant frequency peaks in my signal.”
The agent will guide you to use findpeaks on the spectral output, including advice on setting prominence and distance thresholds.
“How can I perform real-time spectral analysis?”
The agent will direct you to use spectrumAnalyzer or dsp.SpectrumEstimator from the DSP System Toolbox for frame-by-frame processing.
“Which window should I use to resolve two close frequencies?”
The agent will suggest using a Hann or Kaiser window and explain the trade-off between main-lobe width and sidelobe suppression.

Install

  1. Download the skill as a ZIP:

    https://mp-staging.autogineer.com/agent-skills/matlab/matlab-analyze-spectrum/download
  2. Unzip it into the cross-client skills folder of your project:

    mkdir -p .agents/skills && unzip matlab-analyze-spectrum.zip -d .agents/skills

Documentation by MathWorks

Analyze Signal Spectra in MATLAB

Compute, visualize, and extract measurements from signal spectra using Signal Processing Toolbox. Choose the right spectral estimator, avoid common FFT pitfalls, and use built-in measurement functions instead of reinventing them.

When to Use

  • Computing frequency content of a signal (FFT, periodogram, PSD, power spectrum)

  • Estimating power spectral density (Welch, periodogram, parametric)

  • Comparing frequency content of two signals (cross-spectrum, coherence)

  • Finding spectral peaks (dominant frequencies, harmonics)

  • Measuring bandwidth, band power, or occupied bandwidth

  • Visualizing frequency-domain data

  • Streaming (real-time, frame-by-frame) spectral analysis with spectrumAnalyzer, dsp.SpectrumEstimator, dsp.CrossSpectrumEstimator

When NOT to Use

  • Designing or applying filters -- use matlab-design-digital-filter

  • Time-frequency/spectrogram analysis (including spectrumAnalyzer spectrogram ViewType, STFT, reassignment, CWT) -- use the time-frequency analysis skill (TBD)

  • Audio-specific features (MFCC, pitch, auditory models) -- use Audio Toolbox

  • Control system frequency response (Bode, Nyquist) -- use Control System Toolbox

Workflow

  1. Batch or streaming? -- Determine if data is already in workspace (batch) or arrives frame-by-frame (streaming). See below.

  2. Preliminary analysis -- When user provides data, run diagnostics to inform technique selection (see below)

  3. Pre-process -- Check for NaN/gaps (use plomb for non-uniform data). Apply detrend(x) only if DC/trend visibly distorts the spectrum

  4. Choose output type -- PSD, power spectrum, amplitude spectrum, or octave bands (see below)

  5. Choose estimator -- Use preliminary analysis results + Estimator Selection below

  6. Compute spectrum -- Use the chosen function with explicit Fs (if available)

  7. Visualize -- Plot with proper axis labels (Hz, dB or linear units)

  8. Extract metrics -- Use findpeaks, bandpower, obw, powerbw. See references/peak-detection.md

  9. Verify -- Parseval's theorem, known tone levels. See references/power-from-spectrum.md

Batch vs Streaming

Determine the processing mode before choosing tools:

IndicatorModeTools
Entire signal in workspace (vector/matrix)Batchpwelch, pspectrum, periodogram, pburg, etc.
Data arrives frame-by-frame (sensor, ADC, real-time loop)StreamingspectrumAnalyzer, dsp.SpectrumEstimator, dsp.CrossSpectrumEstimator
Need live-updating spectrum displayStreamingspectrumAnalyzer (ViewType: spectrum)
Need spectrum values for real-time decisionsStreamingdsp.SpectrumEstimator (returns numeric output each frame)

If streaming: See references/streaming-spectral-analysis.md for System object APIs, properties, and patterns. DSP System Toolbox is required.

The rest of this skill covers batch processing. For streaming, the reference file provides full guidance including the batch-to-streaming mapping table.

Preliminary Data Analysis

When the user provides signal data (variable, file, or description), run diagnostic checks before recommending a technique. See references/preliminary-analysis.md for full code.

Quick checks (run in order — stop early if decisive):

  1. Sampling type — Fs given → uniform. Time vector with small jitter (<5%) → pspectrum(x,t) (resamples internally). Large jitter or truly non-uniform → plomb

  2. Data quality — NaN/Inf, DC offset, trend → preprocess first

  3. Length — <128: parametric only. 128–1024: periodogram/pburg. >10k: pwelch preferred

  4. Spectral character — Run periodogram (Hann), compute spectral flatness + peak count + strongest peak width:

    • Flatness >0.3 → wideband → pwelch (peaks are just noise fluctuations)

    • Flatness <0.1, strongest peak narrow (width < 10×df) → tonal → periodogram(...,'power') or rootmusic

    • Flatness <0.1, strongest peak wide (width ≥ 10×df) → wideband-structured (chirps, shaped bands) → pwelch + obw

    • Flatness 0.1–0.3, peaks present → mixed → pwelch + findpeaks

  5. AR fitness — Fit AR models with arburg, track prediction error vs order. If error plateaus <10% at low order → pburg is strong candidate

  6. Tone count — If tonal: count peaks with findpeaks, cross-check with eigenvalue drop. Known count + resolution-limited → rootmusic(x,2*nTones,Fs)

Report findings to the user before computing the spectrum:

Preliminary Analysis:
- Sampling: uniform at [Fs] Hz
- Duration: [X] s ([N] samples), resolution: [df] Hz
- Spectral character: [wideband/tonal/mixed]
- AR fit: [quality] (order [N], residual [X]%)
Recommendation: [estimator] because [justification]

Output Type Selection

User's GoalOutput TypeFunction / Option
Compare to a standard or another signal (length-independent)PSD (power/Hz)pwelch (default), periodogram
Read amplitude of a specific tonePower spectrum → sqrtpspectrum(x,Fs) (default); periodogram(x,w,N,Fs,'power') or pwelch(x,w,[],[],Fs,'power') for explicit control
Measure total power in a bandPSD then integrate, or use bandpowerbandpower(x,Fs,[fLow fHigh])
Octave-band levels (acoustics, vibration)Fractional-octave spectrumpoctave(x,Fs,'BandsPerOctave',3)
Detect frequencies in nearly-uniform data (small jitter)Power spectrumpspectrum(x,t) — resamples internally
Detect frequencies in non-uniform/gapped dataLomb-Scargleplomb(x,t)

PSD vs Power Spectrum: PSD (power/Hz) is resolution-independent — broadband noise stays flat when you change parameters. Power spectrum (power/bin) is tone-friendly — peak height equals true tone power. Use PSD for noise characterization and specs (g²/Hz, dBm/Hz); use power spectrum for reading tone amplitudes. Convert: PS = PSD × RBW where RBW = enbw(win)*Fs/segLen.

Estimator Selection

Decision tree (simple → specialized):

  1. Just want to see what frequencies are present? → pspectrum(x,Fs)

  2. Need a smooth, low-variance PSD? → pwelch (favored for long signals — segment averaging reduces variance) or periodogram (short signal, cannot segment)

  3. Need accurate tone amplitude from peaks? → periodogram(x,win,N,Fs,'power') then sqrt

  4. Need octave-band levels (acoustics, vibration)? → poctave(x,Fs,'BandsPerOctave',3)

  5. Comparing two signals at each frequency? → mscohere (related?) / cpsd (shared content + phase)

  6. Data is nearly uniform (small jitter)? → pspectrum(x,t) (resamples internally). Truly non-uniform or large gaps? → plomb(x,t)

  7. Need to resolve sinusoids closer than Fs/N? → rootmusic / pmusic (must know # of sinusoids)

  8. Short data, want low-variance PSD without segmenting? → pmtm (multitaper — uses orthogonal DPSS tapers on full record; no AR assumption)

  9. Short data, want smooth sidelobe-free PSD? → pburg (assumes signal is AR-like)

Quick reference table:

GoalFunctionWhen to use
Quick look at frequency contentpspectrumDefault -- good defaults, tune Leakage (0–1) for resolution vs sidelobes
Single-sided amplitude spectrumperiodogram with 'power'Need amplitude per bin
PSD with low variancepwelchLong signals — averaging segments reduces variance; preferred when signal is long enough to segment
PSD of short signal, low variancepmtmMultitaper — uses full record with orthogonal DPSS tapers; no segmentation needed
PSD of short signalperiodogramToo short to segment; high variance (single window)
Resolve closely-spaced sinusoidspmusic, rootmusicKnown # of sinusoids, super-resolution
Smooth PSD of short AR-like datapburgNo sidelobes; NOT for resolving tones

Default: Start with pspectrum. Drop to pwelch/periodogram for explicit control. Never use manual FFT unless user explicitly asks — redirect to built-in functions.

pspectrum vs pwelch: pspectrum outputs power spectrum (Kaiser beta=20, ~76% overlap). pwelch defaults to PSD. They match within ~0.2 dB using equivalent parameters. See references/pspectrum-equivalence.md for exact mapping. Resolution control in pspectrum: Use FrequencyResolution (Hz) to set target resolution directly, or Leakage (0–1) to control the resolution/sidelobe trade-off indirectly. TimeResolution is spectrogram-only — do NOT use it for power spectrum. Leakage parameter: pspectrum(x,Fs,Leakage=L) where L ∈ [0,1] — 0 = minimum leakage (max sidelobe suppression, widest main lobe), 1 = maximum leakage (rectangular window, best resolution), default 0.5.

Nonparametric vs Parametric: Use nonparametric (FFT-based) when signal is long or spectral shape is unknown. Use parametric when data is short, signal matches an AR model, or you need super-resolution. See references/estimator-comparison.md for detailed trade-offs.

Window Selection

GoalWindowWhy
General-purpose PSDHannGood balance, 50% overlap optimal
Detect weak tone near strong toneBlackman-Harris or Kaiser (beta≥10)Low sidelobes (-92 dB)
Resolve two close frequenciesHann or Kaiser (beta=5)Narrow main lobe
Accurate amplitude measurementFlat Top~0.01 dB scalloping loss
Tunable trade-offKaiser(N,beta)Single parameter controls curve

Key rule: Higher sidelobe suppression costs wider main lobe. Only suppress as much as dynamic range requires. See references/spectral-windows.md for ENBW values and overlap guidelines.

Key Functions

FunctionPurpose
pspectrumPower spectrum with automatic defaults; Leakage parameter (0–1) controls resolution vs sidelobe suppression
periodogramPSD or power spectrum via single-window DFT
pwelchPSD via Welch's averaged periodogram
pmtmPSD via Thomson's multitaper method (DPSS/Slepian tapers)
cpsdCross power spectral density
mscohereMagnitude-squared coherence (0 to 1)
pburg / pyulearAR PSD (Burg / Yule-Walker)
pmusic / peigPseudospectrum (MUSIC / eigenvector)
rootmusicFrequency estimation via root-MUSIC
findpeaksLocate spectral peaks
refinepeaksSub-bin peak frequency/amplitude refinement (use after findpeaks)
bandpowerPower in a frequency band
obw / powerbwOccupied bandwidth / 3-dB power bandwidth
meanfreq / medfreqMean / median frequency of spectrum
sfdrSpurious free dynamic range
toiThird-order intercept point (two-tone intermodulation)
spectralFlatnessSpectral flatness (0=tonal, 1=white noise)
spectralCrestSpectral peak-to-mean ratio
spectralKurtosisSpectral kurtosis (transient/non-stationarity detection)
poctaveFractional-octave spectrum
plombLomb-Scargle periodogram (non-uniform data)
spectralEntropySpectral entropy (replaces deprecated pentropy)
spectrumAnalyzerLive streaming spectrum/spectrogram display (DSP System Toolbox)
dsp.SpectrumEstimatorStreaming PSD/PS with numeric output (DSP System Toolbox)
dsp.CrossSpectrumEstimatorStreaming cross-spectrum between two signals (DSP System Toolbox)

Patterns

Convention reminders (apply to ALL code below):

  • pow2db(x) for dB — NEVER 10*log10(x)

  • detrend(x) only when DC offset or trend is clearly an issue (e.g., large DC spike dominates spectrum)

Single-Sided Amplitude Spectrum
Fs = 1000;
N = length(x);
win = hann(N);

% periodogram with 'power' gives single-sided power per bin
% NFFT=N keeps df=Fs/N ([] defaults to nextpow2 which shifts grid)
[pxx,f] = periodogram(x,win,N,Fs,'power');
ampSpectrum = sqrt(pxx);  % power → RMS amplitude

figure
plot(f,ampSpectrum)
xlabel("Frequency (Hz)")
ylabel("Amplitude (RMS)")
title("Single-Sided Amplitude Spectrum")

Key points:

  • Window correction handled automatically by periodogram

  • Frequency resolution is always Fs/N — zero-padding only interpolates

  • For manual FFT: multiply by 2/N for single-sided amplitude, do NOT double DC

PSD with Welch's Method
Fs = 44100;
% x = detrend(x);  % uncomment if DC/trend distorts low-frequency estimates
segmentLength = round(Fs * 0.05);  % 50 ms → df = 20 Hz
win = hann(segmentLength);

[pxx,f] = pwelch(x,win,round(0.5*segmentLength),[],Fs);

figure
plot(f,pow2db(pxx))
xlabel("Frequency (Hz)")
ylabel("PSD (dB/Hz)")
title("Power Spectral Density (Welch)")
grid on

Key points:

  • Segment length sets resolution: df = Fs / segmentLength

  • More segments = lower variance but coarser resolution (trade-off)

  • 50% overlap with Hann is optimal

Spectral Peak Detection

See references/peak-detection.md for noise-floor-relative thresholds and harmonic detection.

[pxx,f] = pwelch(x,hann(1024),[],[],Fs);
pxxDB = pow2db(pxx);

[peaks,locs] = findpeaks(pxxDB,f, ...
    MinPeakHeight=-20, ...
    MinPeakDistance=50, ...
    MinPeakProminence=10);

figure
plot(f,pxxDB)
hold on
plot(locs,peaks,"rv",MarkerFaceColor="r")
hold off
xlabel("Frequency (Hz)")
ylabel("Power/Frequency (dB/Hz)")
legend("PSD","Peaks")
Band Power and Bandwidth

See references/spectral-measurements.md for full details including PSD input, power spectrum + rbw input, and spectral entropy.

Fs = 1000;
pTotal = bandpower(x,Fs,[0 Fs/2]);
pBand = bandpower(x,Fs,[50 150]);
fracPower = pBand / pTotal;

bw = obw(x,Fs);       % 99% occupied bandwidth
pbw = powerbw(x,Fs);  % 3-dB bandwidth (narrowband/resonance signals only)
Fractional-Octave Spectrum (poctave)

See references/poctave-api.md for full syntax, common mistakes, and octave smoothing pattern.

Fs = 44100;

% 1/3-octave with A-weighting (environmental noise)
[p,cf] = poctave(x,Fs,'BandsPerOctave',3,'Weighting','A','FrequencyLimits',[20 20000]);

% From existing PSD (no recomputation needed)
[pxx,f] = pwelch(x,[],[],[],Fs);
[p,cf] = poctave(pxx,Fs,f,'BandsPerOctave',3);

Key points:

  • Use 'BandsPerOctave',3 — do NOT use '1/3 octave' (invalid syntax)

  • Only valid types: 'power' (default), 'spectrogram' — NO 'psd' type

  • Can accept pre-computed PSD: poctave(pxx,Fs,f) avoids reprocessing

  • Output is always power — use pow2db(p) for dB display

Cross-Spectral Comparison
Fs = 1000;
segLen = 1024;
win = hann(segLen);

[pxy,f] = cpsd(x,y,win,segLen/2,[],Fs);
[cxy,f] = mscohere(x,y,win,segLen/2,[],Fs);

figure
tiledlayout(2,1)
nexttile
plot(f,pow2db(abs(pxy)))
ylabel("|CPSD| (dB)"); xlabel("Frequency (Hz)")
nexttile
plot(f,cxy)
ylabel("Coherence"); xlabel("Frequency (Hz)"); ylim([0 1])

Key points:

  • cpsd is complex — use abs for magnitude, angle for phase lag

  • Coherence ≈ 1: linearly related; ≈ 0: unrelated at that frequency

  • Use same win/overlap as pwelch for consistent resolution

Multitaper PSD (Thomson's Method)
Fs = 1000;
nw = 4;  % time-halfbandwidth product (default); resolution bandwidth = 2*nw*Fs/N
[pxx,f] = pmtm(x,nw,[],Fs);

plot(f,pow2db(pxx))
xlabel("Frequency (Hz)"); ylabel("PSD (dB/Hz)")

Key points:

  • Uses 2*nw-1 orthogonal DPSS (Slepian) tapers on the full record — no segmentation

  • Variance reduction without sacrificing frequency resolution (unlike Welch)

  • Resolution bandwidth = 2*nw*Fs/N — increase nw for smoother PSD (wider bandwidth), decrease for finer resolution

  • Ideal for short records where Welch can't segment, or when you need maximum spectral information from limited data

  • Output is true PSD — directly comparable to pwelch

  • Confidence intervals available: [pxx,f,pxxc] = pmtm(x,nw,[],Fs,ConfidenceLevel=0.95)

  • When to prefer over pwelch: short data, can't afford to segment, or need optimal bias-variance without AR assumptions

  • When to prefer over pburg: no prior knowledge that signal is AR-like; want a nonparametric estimate

Parametric PSD (Burg Method)
Fs = 1000;
modelOrder = 16;  % 2*numPeaks to 4*numPeaks
[pxx,f] = pburg(x,modelOrder,[],Fs);

plot(f,pow2db(pxx))
xlabel("Frequency (Hz)"); ylabel("PSD (dB/Hz)")

Key points:

  • Order too low = over-smoothed; too high = spurious peaks

  • Burg preferred over Yule-Walker (less biased for short data)

  • Output is true PSD — comparable to pwelch

Frequency Estimation with MUSIC
Fs = 1000;
nSinusoids = 2;
p = 2*nSinusoids;  % subspace dim: 2 per real sinusoid (pos + neg freq)

[pseudoSpectrum,f] = pmusic(x,p,1024,Fs);
freqEstimates = rootmusic(x,p,Fs);  % more accurate than reading peaks

Key points:

  • MUSIC output is a pseudospectrum, NOT a PSD — peak heights ≠ power

  • p = 2 × number of real sinusoids — using p = nSinusoids gives wrong results

  • rootmusic gives more accurate frequencies than reading pmusic peaks

  • Works best for sinusoids in white noise; short data OK if SNR adequate

Manual FFT-Based PSD (When User Insists)

Only when user explicitly needs manual FFT. See references/manual-fft-psd.md for windowed, power spectrum, and complex-valued variants.

Formula: psdx = (1/(Fs*N)) * abs(fft(x)(1:N/2+1)).^2, double interior bins only (not DC/Nyquist). Windowed: normalize by 1/(Fs*sum(win.^2)).

Conventions

  • Use pspectrum as default — redirect manual FFT users to built-in functions

  • Detrend when warranted — apply detrend(x) when DC/trend visibly dominates the spectrum or distorts low-frequency estimates; do not blindly detrend every signal

  • Label axes with units — "Frequency (Hz)", "PSD (dB/Hz)", "Amplitude (V)"

  • pow2db/db2pow for dB — never 10*log10(...) manually. See references/units-and-db.md for domain-specific reference levels

  • Frequency resolution is Fs/N — zero-padding only interpolates

  • Use built-in measurement functions — bandpower, obw, powerbw, findpeaks

  • Favor convenience plots — calling pwelch(x,[],[],[],Fs), pspectrum(x,Fs), pburg(x,order,[],Fs), etc. without output arguments generates a labeled plot automatically; prefer this over manual plotting when a quick visualization is needed

  • Write a script — when performing multi-step analysis, write the complete workflow as a .m script and run it with run_matlab_file, rather than executing code piecemeal across multiple evaluate_matlab_code calls. This produces a reviewable, reproducible artifact

Common Mistakes

MistakeCorrect Approach
fft(x) without windowApply hann(N) or similar
abs(fft(x)).^2 as "PSD"Use periodogram/pwelch; if manual, scale by 1/(Fs*N) and double interior only
Confusing PSD with power spectrumPSD = power/Hz; PS = power/bin
Zero-padded FFT = higher resolutionZero-padding interpolates, doesn't resolve
10*log10(pxx)Use pow2db
Manual integration for band powerUse bandpower(x,Fs,[fLow fHigh])
Comparing pspectrum to pwelch directlyDifferent units (PS vs PSD). See references/pspectrum-equivalence.md
Using pentropyDeprecated — use spectralEntropy instead
pspectrum(...,'TimeResolution',val) for power spectrumTimeResolution is spectrogram-only. Use 'FrequencyResolution',val or 'Leakage',val
obw(x,Fs,[],'Percentpower',pct)obw uses positional args, not name-value. Correct: obw(x,Fs,[fLow fHigh],pct) where pct is the 4th positional arg
Filter-bank tone power reads 1–3 dB lowTone is between bins — filter-bank has narrow pass-bands. Fix: coherent frame length (frameLen = Fs/df) so tones land on bin centers
snr(x,Fs) unstable for multi-tone with harmonic relationshipssnr classifies harmonics specially — 200 Hz = 2nd harmonic of 100 Hz causes inconsistent results. Use per-tone SNR from spectrum instead

Troubleshooting

See references/troubleshooting.md for detailed explanations and code examples.

  • "Two tones but only one peak" — Resolution limit (Fs/N) or sidelobe masking. Fix: more data, lower-sidelobe window (blackmanharris, kaiser(N,15)), or reassigned periodogram ([pxx,f,rpxx,fc] = periodogram(x,win,N,Fs,'reassigned') — 4-output syntax returns reassigned PSD and center-of-energy frequencies for sharper peak localization).

  • "Tone power is wrong" — Scalloping loss (tone between bins). Fix: flattopwin(N) for amplitude accuracy. Worst case: Hann loses 1.4 dB, Flat Top loses 0.01 dB.

Verification

Verify with Parseval's theorem — total power from spectrum should match time-domain. See references/power-from-spectrum.md for formulas.

pTime = sum(x.^2)/length(x);
pFreq = sum(pxx) * (f(2)-f(1));  % integrate PSD
assert(abs(pTime - pFreq)/pTime < 0.02,"Parseval check failed")

Copyright 2026 The MathWorks, Inc.


Files

references/preliminary-analysis.md

Preliminary Data Analysis

When a user presents signal data, run these diagnostic steps before recommending a spectral analysis technique. Report findings to the user and use them to justify your estimator choice.

Step 1: Sampling Characterization

Determine whether data is uniformly or non-uniformly sampled.

% Check what the user provided
if exist('Fs','var')
    samplingType = "uniform";
    dt = 1/Fs;
elseif exist('t','var')
    dtVec = diff(t);
    jitter = std(dtVec) / mean(dtVec);
    if jitter < 0.01
        samplingType = "uniform";
        Fs = 1/mean(dtVec);
    elseif jitter < 0.05
        samplingType = "nearly-uniform";  % small jitter — pspectrum can resample
        Fs = 1/mean(dtVec);
    else
        samplingType = "non-uniform";
    end
end

Decision:

  • samplingType == "uniform" → all estimators viable (proceed to next steps)

  • samplingType == "nearly-uniform" (jitter 1–5%) → pspectrum(x,t) handles this automatically (resamples to uniform internally), then computes spectrum. All other estimators also work after explicit resampling with resample(x,t,Fs).

  • samplingType == "non-uniform" (jitter >5% or irregular gaps) → use plomb(x,t). Stop here for estimator selection (other methods require uniform sampling).

Step 2: Data Quality

Check for issues that affect spectral estimates.

N = length(x);
hasNaN = any(isnan(x));
hasInf = any(isinf(x));
dcLevel = mean(x);
hasTrend = abs(mean(diff(x))) > 1e-10 * std(x);

fprintf("Samples: %d (duration: %.3f s)\n",N,N/Fs)
fprintf("DC level: %.4g\n",dcLevel)
fprintf("Has NaN: %s | Has Inf: %s | Has trend: %s\n", ...
    string(hasNaN),string(hasInf),string(hasTrend))

Actions:

  • NaN/Inf present → interpolate (fillmissing) or use plomb with valid indices

  • DC offset or trend clearly distorting spectrum → apply detrend(x). Do not detrend blindly

  • Very short signal (N < 64) → parametric methods (pburg) or rootmusic only

Step 3: Signal Length Classification

Signal length relative to sample rate determines which estimators are viable.

duration = N / Fs;
segLenWelch = round(N/8);  % typical Welch: 8 segments
dfWelch = Fs / segLenWelch;
dfFull = Fs / N;

fprintf("Duration: %.3f s | N = %d samples\n",duration,N)
fprintf("Full-length resolution: %.2f Hz\n",dfFull)
fprintf("Welch resolution (8 segments): %.2f Hz\n",dfWelch)

if N < 128
    lengthClass = "very-short";
elseif N < 1024
    lengthClass = "short";
elseif N < 10000
    lengthClass = "medium";
else
    lengthClass = "long";
end

Decision:

Length ClassViable Estimators
very-short (<128)pburg, pmtm, rootmusic only
short (128–1024)pspectrum (quick look), pmtm (low-variance without segmenting), periodogram, pburg, rootmusic
medium (1024–10k)pspectrum (quick look), periodogram, pwelch (few segments), pmtm, pburg
long (>10k)pspectrum (quick look), pwelch (preferred for low-variance PSD), periodogram, pmtm, pburg

Step 4: Spectral Character (Wideband vs Narrowband)

Use a quick pspectrum to classify spectral shape. A tonal signal has sharp peaks; a wideband signal has energy spread across many frequencies.

% Detrend only if Step 2 identified DC/trend distortion
% x = detrend(x);

% Use periodogram (Hann) for classification — pspectrum's Kaiser window
% widens peaks and distorts width measurements
[pxx,f] = periodogram(x,hann(N),N,Fs);
pxxDB = pow2db(pxx);
df = f(2) - f(1);

% Spectral flatness: 1 = white noise, near 0 = tonal/concentrated
spectralFlat = db2pow(mean(pxxDB)) / mean(db2pow(pxxDB));

% Peak detection with width measurement
noiseFloor = median(pxxDB);
[peaks,peakFreqs,peakWidths,peakProms] = findpeaks(pxxDB,f,MinPeakProminence=10);
nProminentPeaks = numel(peaks);

% Peak width check: use the STRONGEST peak (most prominent) to distinguish
% narrow tones (~3-4 bins with Hann) from broadband bands/chirps (>>10 bins)
if nProminentPeaks >= 1
    [~,iStrongest] = max(peakProms);
    strongestWidth = peakWidths(iStrongest);  % Hz (half-prominence width)
    isNarrowPeaks = strongestWidth < 10*df;
else
    strongestWidth = 0;
    isNarrowPeaks = false;
end

fprintf("Spectral flatness: %.4f (1=white noise, 0=concentrated)\n",spectralFlat)
fprintf("Noise floor: %.1f dB | Prominent peaks: %d | Strongest peak width: %.1f Hz (%.1f bins)\n", ...
    noiseFloor,nProminentPeaks,strongestWidth,strongestWidth/df)

if spectralFlat > 0.3
    spectralChar = "wideband-noise";  % flat spectrum dominates; peaks are noise fluctuations
elseif nProminentPeaks >= 1 && spectralFlat < 0.1 && isNarrowPeaks
    spectralChar = "tonal";           % narrow peaks = discrete tones
elseif nProminentPeaks >= 1 && spectralFlat < 0.1 && ~isNarrowPeaks
    spectralChar = "wideband-structured";  % wide peaks = chirps, bands, or shaped noise
elseif nProminentPeaks >= 1
    spectralChar = "mixed";  % tones in noise (flatness 0.1–0.3)
else
    spectralChar = "wideband-structured";  % shaped but no prominent peaks
end

Decision:

  • "wideband-noise" → pwelch for low-variance PSD (averaging reduces variance on broadband)

  • "tonal" → pspectrum(x,Fs) for quick look; periodogram with 'power' for accurate tone amplitudes; rootmusic if resolution-limited

  • "mixed" → pspectrum(x,Fs) for initial exploration; pwelch for noise floor + findpeaks for tones; pburg if short

  • "wideband-structured" → pwelch or poctave (if octave-band view appropriate)

Step 5: AR Model Fitness Test

Determine if a low-order autoregressive model explains the signal well. If yes, pburg gives a clean, sidelobe-free PSD.

% Fit AR models of increasing order, track prediction error
maxOrder = min(32,round(N/4));
orders = 2:2:maxOrder;
predErrors = zeros(size(orders));

for k = 1:numel(orders)
    [~,predErrors(k)] = arburg(x,orders(k));
end

% Normalized prediction error (relative to signal variance)
normPE = predErrors / var(x);

% Find "knee" — order beyond which error barely drops
relDrop = -diff(normPE) ./ normPE(1:end-1);
kneeIdx = find(relDrop < 0.05,1,'first');
if isempty(kneeIdx)
    bestOrder = orders(end);
    arFit = "poor";  % error still dropping, not a good AR model
else
    bestOrder = orders(kneeIdx);
    finalPE = normPE(kneeIdx);
    if finalPE < 0.1
        arFit = "excellent";
    elseif finalPE < 0.3
        arFit = "good";
    else
        arFit = "moderate";
    end
end

fprintf("AR fit: %s (best order: %d, residual energy: %.1f%%)\n", ...
    arFit,bestOrder,normPE(kneeIdx)*100)

Decision:

  • "excellent" or "good" → pburg(x,bestOrder,[],Fs) is a strong candidate (smooth, no sidelobes)

  • "moderate" or "poor" → nonparametric methods preferred (pwelch, periodogram)

Step 6: Discrete Tone Count (for Subspace Methods)

If the signal appears tonal, estimate the number of sinusoidal components. This is required for MUSIC/root-MUSIC.

% Only run if spectral character is tonal or mixed
if ismember(spectralChar,["tonal","mixed"])
    % Use pwelch for smoother PSD (pspectrum oversamples, inflates peak count)
    [pWelch,fWelch] = pwelch(x,hann(min(N,256)),[],[],Fs);
    pWelchDB = pow2db(pWelch);
    [~,locs] = findpeaks(pWelchDB,fWelch, ...
        MinPeakProminence=6, ...
        MinPeakDistance=max(dfFull*3,Fs/100));
    nTones = numel(locs);

    % Cross-check with eigenvalue analysis (MDL criterion)
    if N >= 64
        corrOrder = min(floor(N/2)-1,64);  % must be < N
        [~,~,~,eigvals] = pmusic(x,corrOrder,[],Fs);
        % Sharp drop in eigenvalues separates signal from noise subspace
        logEig = log(eigvals(1:min(30,numel(eigvals))));
        dLogEig = diff(logEig);
        nTonesMDL = find(abs(dLogEig) < 0.5*median(abs(dLogEig)),1,'first');
        if isempty(nTonesMDL)
            nTonesMDL = nTones;  % fallback to peak count
        end
    else
        nTonesMDL = nTones;
    end

    fprintf("Estimated tone count: %d (peak-based) / %d (eigenvalue-based)\n", ...
        nTones,nTonesMDL)
end

Decision:

  • Known/estimated tone count + short data or resolution limit → rootmusic(x,2*nTones,Fs) for frequency estimates

  • Unknown tone count or broadband → stick with nonparametric

Summary: Mapping Diagnostics to Estimator

Diagnostic ResultRecommended Estimator
Quick look (any signal)pspectrum(x,Fs) — good defaults, tune Leakage if needed
Nearly-uniform (small jitter <5%)pspectrum(x,t) — resamples internally; or resample(x,t,Fs) then any estimator
Non-uniform sampling (large jitter/gaps)plomb(x,t)
Very short + tonalrootmusic or pburg
Short + low-variance PSD neededpmtm(x,nw,[],Fs) — no AR assumption, no segmentation
Short + good AR fitpburg(x,order,[],Fs)
Short + poor AR fitpmtm(x,nw,[],Fs) or periodogram(x,win,N,Fs)
Long + widebandpwelch (averaging smooths noise)
Long + tonalperiodogram(x,win,N,Fs,'power') → findpeaks
Tonal + resolution-limited (tones closer than Fs/N)rootmusic(x,2*nTones,Fs)
Mixed (tones + noise)pwelch for noise characterization + findpeaks for tones
Acoustics/vibration (any length)poctave(x,Fs,'BandsPerOctave',3)

Reporting Template

After running the diagnostics, report to the user:

Preliminary Analysis:
- Sampling: [uniform at Fs Hz / non-uniform]
- Duration: X s (N samples)
- Frequency resolution: X Hz (full-length)
- Spectral character: [wideband-noise / tonal / mixed / wideband-structured]
- AR model fit: [excellent/good/moderate/poor] (order N, residual X%)
- Estimated tones: N (if tonal)

Recommendation: [estimator] because [1-sentence justification]

Copyright 2026 The MathWorks, Inc.


Requirements and compatibility

Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta

“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.

Security

No issues found by our static scan· No issues found

A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.

Attribution and licence

Created by
MathWorks
Publisher
MathWorks (Official)
Source
matlab/matlab-agentic-toolkit · skills-catalog/signal-processing/matlab-analyze-spectrum · commit 2a7f215
Licence
BSD 2-Clause License (redistribution allowed)
  • LICENSE (added by AutoGineer from LICENSE.md)
  • ATTRIBUTION.md (written by AutoGineer)
Synced

AutoGineer is not affiliated with or endorsed by MathWorks.

Download ZIP

The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download matlab-analyze-spectrum.zip